時事問題生成 のしくみ ← ガイダンス一覧 アプリを開く

時事問題生成 のしくみ

― 16人の担当者に分けて読む ―
jiji-mondai-opus.akuramochi.chatgpt.site
テーマと期間を指定すると、ウェブを調べて日経TEST水準の時事演習8問を作るWebアプリ

この資料の読み方

プログラムを書かない人が、「中で何が、どの順番で、どう動いているか」を自分の言葉で説明できるようになることを目標にしています。用語は3章の「用語ミニ辞典」で先にすべて説明してから使います。本文中で このように 点線の付いた言葉は、その辞典に載っている言葉です。

4章では、このアプリを16人の担当者が並んだ工場のラインと見立てて、1人1ページで説明します。各ページには流れの図(誰から何が来て、どこへ何が出るか)と、左=やさしい言い方/右=同じことを指す技術用語を並べた表が入っています。右側は、あとで自分で調べるための手がかりです。丸数字が橙色の担当は生成AIを呼び、紺色の担当は呼びません。16人のうち、AIを呼ぶのは5人だけです。その意味は6章で説明します。

対象システム: 時事問題生成(個人用 時事教材づくりWebアプリ/current-affairs-mvp)
作成日: 2026年8月11日 / 全29ページ / 想定読者: 非エンジニア(大学1年生程度の予備知識)

1章

これは何をするアプリか

「テーマ」と「いつからいつまで」を入れると、アプリがウェブを調べ、その期間のニュースから演習問題を8問つくって画面に出す――それだけのアプリです。目的は暗記のテストではありません。ニュースを仕事の判断につながる形で覚え直すことです。

できあがるもの(1回ぶんの中身は毎回この形に固定)

押さえる構造4件。事実ではなく、事実を貫く力学。「命題・メカニズム・なぜ重要か・仕事への接続」の4点セット。
本文(リード文)1つ。700〜900字。中に空欄が2か所(A・B)と下線が2か所(a・b)が埋め込まれる。
設問8問。4択が7問、80字の論述が1問。番号ごとに出題形式まで決まっている(⑪)。
復習資料用語集8件年表6件、それに出典の一覧(最大20件。すべてURL付き)。

使う人の流れ

  1. テーマを書く(最大400字)。例:「日本企業から見たウクライナ情勢とエネルギー安全保障」。
  2. 開始日と終了日を選ぶ。最初から「30日前〜今日」が入っている。難易度は日経TEST水準で固定。
  3. 使うAIを選ぶ。Sonnet 5(既定)Opus 5
  4. ボタンを押すと、9つの工程が1つずつ進む。今どこにいるか、各工程が何秒かかったかが画面に出る。
  5. 途中で失敗したら、その工程だけやり直せる。前の工程の成果は消えない。
  6. 完成すると、下に問題集が現れる。空欄を押すと答えが出る。設問の (a) を押すと本文の該当箇所へ飛ぶ。
  7. 過去の生成は履歴に残り、開き直せる。作業中のものは、画面を開き直すと自動で続きから進む。

この設計から読み取れること

時事問題生成 のしくみ2

2章

全体像を1枚の図で

あなた(ブラウザ) スマホ / PC サインインの壁 配信基盤側。アプリの外 貸倉庫の入口(Worker) worker/index.ts あなたのブラウザの中で動く ① 受付カウンター テーマ・期間・モデルを受け取り1回だけ送る ② 進行役 「次へ進めて」を9回叩く。走行中は2.5秒ごとに 見に行く。閉じても続きから再開できる ⑯ 表示係(固定レンダラー) 完成したJSONを決まった型に流し込む 貸倉庫(Cloudflare Worker)の中 ③ 入力の門番と整理券発行 3〜400字・日付の形・モデル名を検査 → 依頼票1枚 ⑤ 現場監督 1回の呼び出しで、必ず1工程だけ動かす ⑥ 鍵預かり係 条件付きで1行書き換え、勝った人だけ通す ⑦ 工程の割り振り係(9工程の一覧を持つ) AIを呼ぶ担当(Claude Sonnet 5 / Opus 5) ⑧ 調査計画係 3つの調査単位に割る ⑨ 情報収集係 検索 最大3回 × 3工程 ⑩ 設計図係 本文・構造・用語・年表 ⑪ 設問の作成係 4問ずつ2回に分けて ⑬ 内部の直し係 形式違反は理由を添えて1回だけ作り直させる/通信不良は700ミリ秒待って1回だけ再送 Anthropic API api.anthropic.com/v1/messages claude-sonnet-5 / claude-opus-5 返す形をJSONスキーマで固定 ウェブ検索(道具) web_search_20250305 1工程あたり最大3回 AIを使わない検査と保存 ⑫ 公平さの検査係 正解位置をテーマから計算して 配る/言い換えを見抜く ⑭ 最終検品と組立係 16項目を数えて確かめる 1つでも駄目なら完成にしない ⑮ 台帳係 工程ごとに成果・秒数・失敗を まとめて1回で書き込む ④ 保存庫の管理人 ─ Cloudflare D1(表3つ:依頼票 / 工程の記録 / 完成した問題集) 表が無ければその場で作る。model列が無い古い倉庫には、その場で1列だけ足す 完成品と進捗は②へ返る

紺色の枠=AIを呼ばない担当/橙色の枠=AIを呼ぶ担当。点線の枠は「どこで動いているか」の区切り。

時事問題生成 のしくみ3

3章

用語ミニ辞典(この先で使う言葉)

この先の説明は、なるべくこの17語だけで書いています。分からなくなったらこのページに戻ってください。

サーバー頼まれた仕事をこなすために待ち構えているコンピュータ。このアプリのサーバーは自宅のPCではなく、インターネット上の貸倉庫(Cloudflare)にあります。
ブラウザスマホやPCでWebを見る画面(Safari、Chromeなど)。お客さん側。このアプリでは、工程を1つずつ進める指揮もここでやっています(②)。
APIプログラム同士の受付窓口。「決まった形で頼むと、決まった形で返ってくる」。人間向けの画面ではなく機械向けの窓口です。AIの会社にもこの窓口があります。
JSONデータの書き方の一種。ラベルの付いた箱にデータを入れた形。例:「theme は 半導体、periodTo は 2026-08-11」。人も機械も読めます。
JSONスキーマ「返してほしいJSONの設計図」を機械が読める形で書いたもの。AIに渡すと、その形以外では返せなくなります。このアプリの土台です。
データベース表の形でデータを溜めておく箱。ここでは Cloudflare D1(中身はSQLiteという小さなデータベース)の3つの表を指します。
正本(せいほん)どれが本物か、迷った時に見るべき原本。このアプリの正本は、依頼票の1行に入っている「途中経過」です。画面が持っているのは、その写しにすぎません。
ジョブ(依頼票)「このテーマで作ってください」という注文1件ぶんの伝票。番号・入力・使うモデル・今どこまで進んだか・途中経過が、この1行に全部入っています。
工程(ステップ)依頼を最後まで運ぶために割った作業の1区切り。このアプリは9つに割ってあります。1回の呼び出しで進むのは必ず1工程だけ。
状態(ステータス)依頼票が今どの札を付けているか。待ち・走行中・要再試行・完成の4つ。次に何ができるかは、この札だけで決まります。
排他制御整理券。同じものを2人が同時にいじると壊れるので、順番に1人ずつ通す仕組み。このアプリは「鍵の台帳に条件付きで書き込めた人だけが通れる」方式です(⑥)。
バリデーション送られてきたもの・返ってきたものが条件を満たしているか、機械が点検すること。人の目ではなくプログラムが行います。このアプリの半分はこれです。
大規模言語モデル
(LLM)
大量の文章から言葉の続き方を学んだ、いわゆる生成AI。ここで使うのは Claude の Sonnet 5Opus 5。あとの方が丁寧に考えますが、遅く高くなります。
道具を使わせる
(ツール利用)
AIに「自分で調べていいですよ」と道具を渡すこと。ここではウェブ検索。AIは記憶だけで答えず、検索してから書きます。
トークンAIが文章を数える単位。だいたい1文字〜数文字で1つ。「最大9,000トークンまで」は、書ける分量と費用の上限を決めることです。
プロンプト
(指示書き)
AIへの頼み方の文章。このアプリは作問のルールを27,787字の文書(docs/prompt-v3.md)にまとめ、毎回まるごと同封しています。
サーバーレス自分でサーバー機を持たず、頼まれた時だけ他社の設備の上で数百ミリ秒だけ動くやり方。使わない間は何も動いておらず、費用もかかりません。
時事問題生成 のしくみ4

受付カウンター AIを使わない 入口

StudioClient / submit / app/studio-client.tsx

テーマ・期間・使うAIを受け取り、明らかにおかしい入力だけをその場で止めて、サーバーに「作ってください」と1回だけ頼む担当。ボタンを押した後の進行は、すぐ②に引き継ぎます。

あなた ──[テーマ・開始日・終了日・モデル]──▶ ① 受付カウンター(画面の中)
   《3文字未満 / 開始日が終了日より後 → ここで赤字。サーバーへは送らない》
     └──[POST /api/jobs に JSON で1回]──▶ ③ 入力の門番
              └──[202番+依頼票]──▶ ② 進行役へバトンタッチ
受け取るものテーマ(最大400字)、開始日、終了日、使うモデル
出すもの/api/jobs への依頼1件(JSON)
AI使わない
失敗した時画面に赤い帯が出るだけ。サーバーには何も届かない
いつ動くか誰が・何を・なぜ(やさしく言うと)技術のことば(入り口)
画面を開いた瞬間開始日に「日本時間の30日前」、終了日に「日本時間の今日」を入れておく。なぜ:指定する期間は毎回ほぼ同じで、空欄から考えさせる意味がないため初期値(初期状態)。jstDate(-30)。閲覧者の時差を足し引きして日本時間の日付を作っている
文字を打っている間入力欄そのものが400字で打ち止まる。あふれる前に止める入力制約(maxLength)。画面側の制限。迂回されても③が同じ条件でもう一度見る
「このテーマで問題を作る」を押した瞬間テーマ3文字未満/開始日が終了日より後、の2つを見て、赤字を出して止める。なぜ:往復1回ぶんの待ち時間と費用を無駄にしないためクライアントサイドバリデーション。速さのための検査であって、守りではない。守りは③の仕事
検査を通った直後4つの値をJSONに詰めて、1回だけ送るfetch / POST。content-type: application/json と書いて「これはJSONです」と伝える
202番が返ってきた瞬間「受け付けた、まだ終わっていない」という返事。依頼票だけ握って②に渡すHTTP 202 Accepted。時間のかかる処理の受付。200(完了)とは意味が違う
処理が走っている間モデルの選択欄を丸ごと押せなくする。なぜ:途中でモデルが変わると、工程どうしで辻褄が合わなくなるため状態に応じたUIの無効化(<fieldset disabled>)。禁じたい操作は、注意書きではなく押せなくする
たとえるなら 窓口で申込書を受け取る係。名前が空欄なら、その場で突き返します。奥の職人に回すのは、書式が整ってからです。
4章 出演者カタログ5

進行役 AIを使わない 指揮

driveJob / app/studio-client.tsx

このアプリで一番変わっている担当。9つの工程を1つずつ進めるのは、サーバーではなくあなたのブラウザです。依頼票を握って「次へ進めて」を9回叩き、待ちの間は2.5秒ごとに様子を見に行きます。

② 進行役 ──[POST /api/jobs/{id}/advance]──▶ ⑤ 現場監督 ──▶ 1工程だけ実行
    ▲                                                    │
    └────────────[更新後の状態]──────────────┘
  《返事が「走行中」のまま → 2,500ミリ秒待って GET で見に行く》
  《完成 or 要再試行 になるまで繰り返す。要再試行なら赤帯+再試行ボタン》
受け取るもの依頼票(ジョブID)と、いまの状態
出すものadvance の呼び出し(最大9回)と、画面の進捗表示
AI使わない(呼ぶのは、この先の担当)
失敗した時その工程で止まる。それまでの成果は保存済み
いつ動くか誰が・何を・なぜ(やさしく言うと)技術のことば(入り口)
依頼票を受け取った瞬間「いま走らせている」印を1つ立てる。なぜ:履歴から同じ依頼を2回開いても、進行役が2人にならないようにするため再入防止(useRef によるガード)。画面が描き直されても消えない置き場所を使う
1工程ごと「次へ進めて」を1回叩く。1回の呼び出しで進むのは必ず1工程だけ長時間処理の分割。1回のリクエストを短く保つ。貸倉庫には実行時間の上限があるため
返事が「走行中」だった時2,500ミリ秒待って、今度は読むだけの窓口に様子を見に行く。なぜ:別のタブが同じ工程を走らせている最中だからポーリング(一定間隔での問い合わせ)。押しかけずに待つ。常時つなぎっぱなしにするより作りが単純
画面を開いた直後(毎回)履歴を読み、完成も失敗もしていない依頼があれば自動で拾って続きから進めるレジューム(再開)。工程ごとに保存してあるから成り立つ。⑮が土台
完成した瞬間80ミリ秒おいてから、できあがった問題まで滑らかに移動するscrollIntoView({behavior:"smooth"})描画が終わるのを待つためのわずかな遅延
途中で通信が切れた時最新の状態を取り直して画面に出す。ブラウザを閉じても、保存済みの地点は消えない真実の所在(source of truth)。画面の表示は写し。正しい状態は常にサーバーの台帳にある
たとえるなら 工場の外にいる発注者が、電話で「次の工程お願いします」と9回かける。職人は電話が来た時だけ動きます。発注者が席を外しても、作りかけの品物は工場に残っています。
4章 出演者カタログ6

入力の門番と整理券発行 AIを使わない 書く

parseGenerationInput / parseWorkflowModel / lib/request.ts + POST /api/jobs / createJob

サーバー側で最初に動く担当。①と同じ検査をもう一度やり、通ったら依頼票を1枚作って番号を振ります。ここから先、9工程の成果はすべてこの1行にぶら下がります。

① 受付カウンター ──[JSON]──▶ ③ 門番
   《テーマ3〜400字 / 日付の形 / 開始日≦終了日 / モデル名は一覧の2つだけ》
     ├─[違反]──▶ 400番+日本語の理由 ──▶ 画面に赤字
     └─[合格]──▶ 依頼票を1枚(番号・待ち・工程は「調査計画」・0工程目)
                        └──[202番+依頼票]──▶ ② 進行役へ
受け取るもの画面から送られたJSON
出すもの依頼票1件(202番)、または400番+理由
AI使わない
失敗した時依頼票は作られない。データベースには1行も残らない
いつ動くか誰が・何を・なぜ(やさしく言うと)技術のことば(入り口)
依頼が窓口に届いた瞬間画面で検査済みでも、もう一度同じ検査をするなぜ:画面の検査は迂回できる。窓口を直接叩かれても壊れないためサーバーサイドバリデーション。外から来たものは信用しない(untrusted input)という原則
テーマを見る時前後の空白を落としてから3〜400字か数える。空白だけの入力は通らないtrim() と長さ検査。「見た目は入力されている」を弾くための正規化
日付を見る時「2026-08-11」の形をしているかを、文字の並びの型で照合する正規表現 /^\d{4}-\d{2}-\d{2}$/形だけを見ており、暦として実在するかは見ていない(11章)
モデル名を見る時一覧に載っている2つ以外は拒む。指定が無ければ Sonnet 5 にする許可リスト(allowlist)方式。「駄目なものを挙げる」ではなく「良いものだけ挙げる」。抜け道が生まれにくい
合格した直後重複しない番号を発行し、札は「待ち」、工程は「調査計画」、進んだ工程数は0で1行書くUUID(crypto.randomUUID())と状態機械の初期状態。ここが9工程の出発点
返事を返す時200(できた)ではなく202(受け付けた)を返す。中の作業はまだ1つも始まっていないHTTP 202 Accepted。「これから時間をかけて作る」という約束を、番号で表す
たとえるなら 役所の窓口。書式を確かめ、受理印を押し、整理番号の紙を渡します。書類が奥に運ばれるのは、そのあとです。
4章 出演者カタログ7

保存庫の管理人 AIを使わない 書く

getDb / ensureSchema / db/index.ts(+ db/schema.ts, drizzle/)

データの置き場所への出入口を1か所にまとめ、表がまだ無ければその場で作る担当。9工程ぶんの途中経過を預かる棚を、誰も気づかないうちに用意しています。

どの担当も ──[getDb()]──▶ ④ 管理人 ──▶ Cloudflare D1(表3つ+索引6つ)
   《小部屋が立ち上がって最初の1回だけ ensureSchema を実行》
   《model 列が無い古い倉庫 → PRAGMA で列を数え、その場で1列だけ足す》
     └─[道具箱に棚が入っていない]──▶「保存用データベースに接続できませんでした。」
受け取るもの「棚を使いたい」という呼び出しだけ
出すものデータベースへの取っ手(この1本しか出入口がない)
AI使わない
失敗した時その工程は失敗。前の工程までの成果は残る
いつ動くか誰が・何を・なぜ(やさしく言うと)技術のことば(入り口)
棚に触る直前(どの工程でも)外から手渡された道具箱の中に棚があるか確かめ、無ければ日本語で断るバインディング(依存を外から渡す)。接続先をコードに書かない。渡す側を差し替えれば繋ぎ先が変わる
小部屋が立ち上がって最初の1回表と索引を作る指示9つを、まとめて1回で送るCREATE TABLE IF NOT EXISTS既にあれば何もしない=何度やっても結果が同じ(冪等)
その1回を覚えている間「作業中の約束」を1つだけ持ち回し、同時に何本呼ばれても実行は1回に束ねるPromiseのメモ化(シングルフライト)。同じ重い仕事の重複起動を防ぐ定石
その1回が失敗した時覚えた約束を捨てるなぜ:失敗を覚えたままだと、次からずっと「作成済み」と思い込んで表が永久にできないため失敗のキャッシュを避ける。エラー時に状態を巻き戻す。地味だが、これが無いと復旧できない
表ができた直後表の列を数えてmodel 列が無ければその場で足す(既定は Sonnet 5)PRAGMA table_infoALTER TABLE動いているものを止めずに形を変える=マイグレーション
それとは別にdrizzle/ にも同じ形のSQLが2本置いてある。実行時にコードでも作る、という二重構えスキーマ定義の二重管理。片方だけ直すとずれる。11章に挙げた割り切りの1つ
たとえるなら 開店前に、まだ棚が無ければ棚から組み立てる店員。すでに棚があれば何もしません。棚板が1枚足りない古い棚には、その場で1枚だけ足します。
4章 出演者カタログ8

現場監督 AIを使わない 指揮

POST ハンドラ / app/api/jobs/[id]/advance/route.ts

「次へ進めて」という電話を受け、その1回で必ず1工程だけ動かす担当。始める前に鍵を取り、終わったら結果を台帳に書き、作業札を次の工程に貼り替えます。

② 進行役 ──[POST /api/jobs/{id}/advance]──▶ ⑤ 現場監督
   ├─依頼票が無い → 404 / 完成済み → そのまま返す / 要再試行 → 409
   ├─[⑥に鍵を頼む]──取れない──▶ 409「別の処理が進行中です。」
   └─取れた──▶ ⑦ 割り振り係 ──▶ 1工程を実行(ここで秒数を計る)
         ├─成功──▶ ⑮ 台帳へ記録・次の工程へ ──[更新後の状態]──▶ ②
         └─失敗──▶ ⑮ 失敗も記録 ──▶ 422+日本語の理由
受け取るもの依頼票の番号(住所の一部として届く)
出すもの更新後の状態(JSON)。工程ごとの秒数もここに含まれる
AI使わない(呼ぶのは⑦の先の担当)
失敗した時その工程だけ失敗。前の工程の成果は残り、そこから再試行できる
いつ動くか誰が・何を・なぜ(やさしく言うと)技術のことば(入り口)
電話が鳴った瞬間台帳から今の状態を読む。無ければ404で終わりHTTP 404。「その依頼票は存在しない」。500(こちらの不具合)と区別する
状態を見た直後完成済みなら何もせずそのまま返す。何度叩かれても壊れない冪等(べきとう。何度やっても結果が同じ)。②が二重に呼んでも安全にするための性質
要再試行だった時409を返し、勝手に再開しないなぜ:やり直すかどうかは人が決めることだからHTTP 409 Conflict。自動での再試行(⑬)と、人が押す再試行(⑮)の線引きがここ
実行に入る直前⑥に鍵を頼む。取れなければ409で引き返し、何も動かさない排他制御。二重実行の防止。同じ工程でAIを2回呼ぶと、費用も結果も二重になる
実行している間開始時刻を控え、終わったら差を出して所要ミリ秒を記録する。画面の「12.4秒」はこれ計測(Date.now() の差分)。どの工程が重いかを、推測ではなく記録で知る
例外が飛んだ時握りつぶさず、日本語の理由を台帳に書いてから422を返す例外処理と観測可能性。記録に残すから、後から同じ地点をやり直せる
たとえるなら 工場の班長。電話1本につき1工程だけ動かし、作業札を次の工程に貼り替えて、日報に1行書きます。班長自身は品物を作りません。
4章 出演者カタログ9

鍵預かり係 AIを使わない 書く

acquireStep / recoverStaleJob / lib/job-repository.ts

同じ工程が同時に2回走らないようにする担当。鍵の渡し方が独特で、「台帳の1行を条件付きで書き換えて、本当に1行変わった人だけが通れる」という方式をとっています。

⑤ 現場監督 ──[鍵をください]──▶ ⑥ 鍵預かり係
  UPDATE … SET status='running'
   WHERE id=? AND current_step=? AND status IN ('queued','waiting')
    ├─変わった行が 1 → 鍵を渡す(何回目の挑戦かも数えて渡す)
    └─変わった行が 0 → 誰かが先に取った。鍵は渡さない ──▶ 409
  《別口:走行中のまま4分(240,000ミリ秒)動かない行 → 要再試行に落として救出》
受け取るものいまの依頼票
出すもの鍵(何回目の挑戦か・開始時刻)、または「渡せない」
AI使わない
失敗した時呼んだ側が409で引き返すだけ。データは壊れない
いつ動くか誰が・何を・なぜ(やさしく言うと)技術のことば(入り口)
鍵を頼まれた瞬間札が「待ち」か「これから」の時だけ応じる。走行中・完成済みには渡さない状態機械のガード条件。次に進める札が何かを、あらかじめ決めてある
渡すと決めた瞬間「今の札と工程がこのままなら、走行中に変えろ」と1文で書き換える。確かめてから書くのではなく、条件付きで書いて結果を見る楽観的並行性制御(条件付きUPDATE)。「読む→確かめる→書く」の間に横取りされる隙を無くす
書き換えた直後変わった行数が1かどうかだけを見る。1でなければ負け、として静かに引き下がるmeta.changes の判定。「1行だけ変わった」という事実が、勝者であることの証拠になる
鍵を渡す時この工程が過去に何回走ったかを数え、+1した番号を一緒に渡す試行回数(attempt)。台帳側の一意制約(依頼・工程・試行回数)と組んで、二重記録も防ぐ
台帳を読むたび(毎回)走行中のまま4分動きがない行を見つけたら「通信が中断されました」として要再試行に落とすタイムアウトによる回収。見張り役を別に常駐させず、読むついでに掃除するという設計
4分という数字についてコードに直接書かれた固定値。本当に4分を超える工程があると、動いているのに失敗扱いになりうるマジックナンバー。根拠が式ではなく経験則の数値。11章の割り切りの1つ
たとえるなら 会議室の鍵。「空いていたら取る」ではなく、台帳に自分の名前を条件付きで書き込み、書き込めた人だけが入れる方式です。空きを見てから取りに行くと、その隙に取られます。
4章 出演者カタログ10

工程の割り振り係 AIを使わない 指揮

executeWorkflowStep / lib/workflow-runner.ts(+ WORKFLOW_STEPS / lib/workflow-shared.ts)

依頼票に書かれた「今の工程名」を見て、誰を呼ぶかを決めるだけの担当。このファイル1枚を読めば、アプリが何を、何の順でやるかが全部分かります。

⑤ ──[依頼票(今の工程名つき)]──▶ ⑦ 割り振り係
  1 research_plan → ⑧ 調査計画      6 questions_1 → ⑪ 問1〜4
  2 research_1    → ⑨ 情報収集 1/3   7 questions_2 → ⑪ 問5〜8
  3 research_2    → ⑨ 情報収集 2/3   8 questions_3 → 数を数えるだけ(AIなし)
  4 research_3    → ⑨ 情報収集 3/3   9 finalize    → ⑭ 最終検品(AIなし)
  5 blueprint     → ⑩ 設計図
     └──[新しい途中経過と成果物]──▶ ⑤ へ返す(保存するのは⑮)
受け取るもの依頼票(入力・使うモデル・ここまでの途中経過)
出すもの更新した途中経過と、その工程の成果物
AI工程による。9工程のうち6工程で呼ぶ(呼ぶ担当は⑧⑨⑩⑪)
失敗した時材料が揃っていなければ、AIを呼ぶ前に止める
いつ動くか誰が・何を・なぜ(やさしく言うと)技術のことば(入り口)
呼ばれた瞬間途中経過を書き換えずに写しを作るなぜ:途中で失敗した時に、元の内容を汚さないためイミュータブル(変更しない)な扱い。{...state} で複製してから足す
情報収集の工程に入る前調査計画が保存されているか確かめる。無ければAIを呼ばずに止める事前条件(前提の検査)。費用のかかる呼び出しの前に落とす、が鉄則
設計図・設問・最終検品に入る前3回ぶんの調査が全部そろっているか数え、そろっていればその場で1つに束ねる集約(requireResearch)。束ねた結果は保存せず、必要なたびに作り直している
8工程目(questions_3)AIを一切呼ばない。設問が2バッチそろっているかを数えるだけの工程チェックポイント。人が進捗を確認するための区切りでもある
どの工程でも成果物を返すだけで、自分では保存しない責務の分離。「実行する人」と「保存する人」を分けると、両方が単純になる
知らない工程名が来た時「実行する工程を特定できませんでした。」ではっきり止めるフォールスルーの明示。黙って何もしないより、はっきり失敗させるほうが安全
たとえるなら 工程表を1枚だけ持った差配。品物には触らず、「次はあの人」と指さすだけです。工程表を書き換えれば、工場の作り方そのものが変わります。
4章 出演者カタログ11

調査計画係 AIを使う 読む

createResearchPlan / lib/workflow-ai.ts

AIを使う最初の担当。いきなり調べ始めず、テーマを重ならない3つの調査単位に割るための計画だけを作らせます。設問は作らせません。

⑦ ──[テーマ・期間]──▶ ⑧ 調査計画係 ──▶ Claude(Sonnet 5 か Opus 5)
  《返してほしい形を先に渡す:thesis(見立て)+ batches(label・query・focus)》
  《上限 2,000トークン / 考える深さ low / ウェブ検索は渡さない》
    ├─調査単位ちょうど3件、各focus 2件以上 → 合格 ──▶ ⑮ 台帳へ
    └─件数が違う → ⑬ 直し係が理由を添えて1回だけ作り直させる
受け取るものテーマ、開始日、終了日
出すもの調査計画(見立て1つ+調査単位3つ。各単位に検索方針と確認事項)
AI使う。上限2,000トークン、考える深さ low、ウェブ検索なし
失敗した時1回直させても駄目なら、この工程で停止。依頼票は残る
いつ動くか誰が・何を・なぜ(やさしく言うと)技術のことば(入り口)
呼ばれた瞬間「あなたは調査設計者です。設問は作りません」と役割と禁止事項を1文で固定するシステムプロンプト。毎回の指示とは別に、役割を固定する場所がある
頼む時返してほしいJSONの形を、機械が読める仕様として一緒に渡す。書かれていない項目は入れられない構造化出力(JSONスキーマ)。additionalProperties:false で余計な項目も禁じる
同じ時考える深さを「low」、書ける量を2,000トークンに絞る。なぜ:計画は短くてよく、長くすると遅く高くなるためeffortmax_tokens品質・速度・費用を調整する2つのつまみ
返事が来た瞬間中身を見る前に、途中で切れていないかを先に確かめるstop_reasonend_turn(書き切った)以外は、全部失敗として扱う
中身を見る時調査単位がちょうど3件か、各単位の確認事項が2件以上あるかを数える事後条件の検査。形式が合っていても、件数は別に数えないと分からない
数が合わなかった時前回のJSONと「何が駄目だったか」を添えて、もう1回だけ作らせる自己修復ループ(1回限り)。詳しくは⑬。無限に直させない設計
たとえるなら 調べ物を始める前に、図書館で「この3つの棚を、この順で見る」と決める人。棚を決めずに歩き回ると、同じ本を3回開くことになります。
4章 出演者カタログ12

情報収集係 AIを使う 外を読む

researchBatch / mergeResearch / lib/workflow-ai.ts

実際にウェブを検索する唯一の担当。3つの調査単位を、それぞれ別の工程として3回動きます。3回そろったら、出典をURLで名寄せして1本に束ねます。

⑦ ──[調査単位1つ]──▶ ⑨ 情報収集係 ──▶ Claude ──[検索 最大3回]──▶ ウェブ
  《初回は検索を強制。まだ続きがある(pause_turn)なら会話を継いで最大2周》
    └─[900〜1,500字の証拠+出典(最大8件・2件未満は失敗)]──▶ ⑮ 台帳へ
  3回そろった後 ── URLで重複を落とし S01… と採番(最大20件・5件未満は失敗)
受け取るもの調査単位1件(見出し・検索方針・確認事項)と、テーマ・期間
出すもの日本語の証拠パック(900〜1,500字)と、出典一覧
AI使う。上限5,000トークン、考える深さ low、ウェブ検索つき
失敗した時その回だけ失敗。他の2回ぶんの成果は残る
いつ動くか誰が・何を・なぜ(やさしく言うと)技術のことば(入り口)
工程に入った瞬間「政府・公的機関・企業など一次情報を優先」「日付・数値・因果には引用を付ける」「設問は作らない」と条件を固定するシステムプロンプトによる制約。調べ方の方針そのものを、毎回同じ文章で固定している
最初の1回検索を必ず使わせる。なぜ:AIは訓練で覚えた古い記憶だけで、それらしく書いてしまうことがあるためツール強制(tool_choice)。「使ってもよい」ではなく「まず使え」と指定できる
検索している間1工程あたり検索は最大3回まで。3工程あるので、1回の生成で最大9回max_uses: 3道具の使用回数の上限。時間と費用の歯止め
返事が「まだ途中」だった時会話を継ぎ足してもう1周だけ回す。2周で終わらなければ「規定回数内に完了しませんでした」pause_turn と継続呼び出し。長い道具使用を分割して返す仕組みに、こちらが付き合う
出典を拾う時引用ブロックだけでなく、返事の入れ子の奥まで再帰的に探してURLを集め、URLで重複を落とす。引用文は700字で切る再帰探索と重複排除。返り値の形が変わっても拾えるようにした保険でもある
3回そろった後3回ぶんをURLで名寄せし、S01 から番号を振り直す。5件未満なら失敗にする名寄せと下限のしきい値。この番号を、後で設問が「根拠はS03」と指す(⑪⑭)
たとえるなら 3人の調査員に別々の棚を任せ、戻ってきた資料を1つの箱にまとめる編集者。同じ本が3冊来たら1冊にし、通し番号を振り直します。5冊に満たなければ「出直し」です。
4章 出演者カタログ13

設計図係 AIを使う 組み立て

createBlueprint / validateBlueprintMarkup / lib/workflow-ai.ts(+ lib/passage-markup.ts)

設問を書く前に、本文・押さえる構造・用語集・年表という「土台」だけを作らせる担当。ここで本文に空欄と下線の目印を埋め込み、その並び順まで機械が検査します。

⑦ ──[調査結果+出典]──▶ ⑩ 設計図係 ──▶ Claude(正本27,787字を同封)
  《固定:構造4件・本文1つ(700〜900字)・用語8件・年表6件。上限9,000トークン》
  《本文の目印 [[BLANK:A|正答]] [[UNDERLINE:a|文]] … 登場順は必ず A→a→B→b》
    ├─件数と並び順が一致 → 合格 ──▶ ⑮ 台帳へ
    └─ずれ → ⑬ 直し係が1回だけ作り直させる
受け取るものテーマ・期間・3回ぶんの調査結果・出典一覧
出すもの設計図(題名/情報の流動性の注意書き/構造4/本文1/用語8/年表6)
AI使う。上限9,000トークン、考える深さ low
失敗した時1回直しても駄目なら停止。調査結果は残るので、ここから再試行できる
いつ動くか誰が・何を・なぜ(やさしく言うと)技術のことば(入り口)
頼む時docs/prompt-v3.md(作問の正本、27,787字)を丸ごと指示に同封するプロンプトの外部ファイル化。コードを書き換えずに、文章を直すだけで作問方針を変えられる
同じ時正本と食い違う所は「MVP固定仕様を優先」と明記する。なぜ:正本は12〜15問だが、このアプリは8問だから指示の優先順位の明示。矛盾する指示を渡す時は、必ずどちらが勝つかを書く
返ってきた瞬間構造4・本文1・用語8・年表6の件数をそのまま数える。1件でも違えば不合格事後条件の検査。「だいたい合っている」を許さない
本文を見る時HTMLタグではなく [[BLANK:A|正答]] という独自の目印を使わせる。なぜ:見た目を作るのは⑯の仕事で、AIには中身だけ書かせたいから中間表現(マークアップ)。データと見た目の分離。同じデータを画面にも印刷にも使える
目印を数える時空欄はA・Bの2つ、下線はa・bの2つ、しかも本文での登場順が A→a→B→b であることまで見る字句の切り出し(正規表現)と順序の検証。「正本の原則5:記号は本文の登場順に」を機械で担保している
目印の書き損じ切り出した後の普通の文に [[ が残っていたら、壊れていると判断する残骸の検出。壊れた目印は⑯で「そのままの文字列」として画面に出てしまうため
たとえるなら 家を建てる前の間取り図。壁と柱の位置(本文と空欄)を先に決めておかないと、後から窓(設問)をどこに開けるか決められません。
4章 出演者カタログ14

設問の作成係 AIを使う 組み立て

createQuestionBatch / lib/workflow-ai.ts(QUESTION_SPECS)

8問を4問ずつ2回に分けて作らせる担当。どの番号がどの形式かは人があらかじめ決めてあり、AIが書くのは中身だけ。正解は必ず1番目に置かせます。

⑦ ──[設計図+調査結果]──▶ ⑪ 設問の作成係(1回目=問1〜4/2回目=問5〜8)
  Q01 空欄補充      Q02 下線(深掘り)   Q03 共通キーワード  Q04 資料読解[新形式]
  Q05 職業への接続[新形式] Q06 空欄補充  Q07 下線(深掘り)  Q08 論述80字
  《正答は必ず options[0]・correctIndex=0。並べ替えは⑫の仕事》
  《誤答ごとの説明は作らせない(空配列で固定)。解説は140〜220字》
    └─[4問ぶんのJSON]──▶ ⑫の検査 ──▶ ⑮ 台帳へ
受け取るもの設計図、調査結果、出典一覧、何回目のバッチか
出すもの設問4問(設問文・選択肢4・解説・出題理由・出典ID)
AI使う。上限9,000トークン、考える深さ low
失敗した時そのバッチだけ失敗。もう片方の4問は残る
いつ動くか誰が・何を・なぜ(やさしく言うと)技術のことば(入り口)
工程に入った瞬間作る4問の番号・種類・形式を文章で列挙して渡し、指定外のIDは出すなと明記する出力範囲の固定と分割生成。8問を一度に作らせない。長い出力は途中で崩れやすい
選択肢を作らせる時正解を必ず1番目に置かせる。仕様上、それ以外の位置は選べないスキーマの enum による制約。正解の位置を決めるのは機械(⑫)の仕事、と役割を切っている
解説を書かせる時140〜220字。冒頭で正答の中身を述べ、誤答には触れさせない出力仕様の数値化。正本にある「誤答ごとの説明」を、ここでは明示的に打ち消している
出典を付けさせる時使ってよい出典IDを渡し、それ以外は機械が捨てる。1問あたり最大3件。全部捨てられたら先頭の出典を当てる許可リストによる後処理。AIが実在しない番号を作っても、混入しない
下線問題(問2・問7)を作らせる時下線文の言い換えは禁止。背景・因果・二次的影響・企業判断を問う」と目標を1文足す追加制約(depthGoal)。形式だけでなく「難しさの質」を指示で作る。守れたかは⑫が測る
論述(問8)を作らせる時必須キーワード2語と採点観点3件をきっちり要求し、数が違えばその場で不合格事後条件の検査。頼むだけでは守られない、を前提にした二段構え
たとえるなら 8品のコース料理を、前半4皿と後半4皿に分けて別々に仕込む。皿の並び順と品目は先に決まっていて、料理人が決めるのは中身だけです。
4章 出演者カタログ15

公平さの検査係 AIを使わない 検査

answerPattern / lib/problem-schema.ts + positionCorrectOption / isNearParaphrase / lib/question-quality.ts

このアプリでいちばん意外な担当。正解が①ばかりに偏らないよう並べ替え、正答が下線部の言い換えで済んでいないかを見抜きます。どちらもAIではなく、足し算と文字の重なりだけでやっています。

⑪ の出力(正答は必ず1番目) ──▶ ⑫ 公平さの検査係
  《テーマの全文字のコード合計 ÷ 4 の余り = ずらし幅 → 正解位置の並びが決まる》
     例)余りが2 → ③④①②③④①… の順に、4択7問へ順番に配る
  ├─並べ替え:1番目の正答を、決められた位置へ差し込む ──▶ ⑭ へ
  └─言い換え判定:下線文と正答を3文字ずつに刻み、72%以上重なれば差し戻す
  (同じ道具を逆向きにも使う:正答と解説が重ならなければ、解説が別の話 → 差し戻す)
受け取るもの正答が1番目に置かれた設問と、テーマ
出すもの正解位置を散らした設問(誤答どうしの順番はそのまま)
AI使わない。計算と文字比較だけ
失敗した時言い換えと判定されたら工程を失敗させ、⑬に作り直させる
いつ動くか誰が・何を・なぜ(やさしく言うと)技術のことば(入り口)
並べ替える前テーマの文字コードを全部足し、4で割った余りを出す。乱数は使わない決定的アルゴリズム。同じテーマなら毎回同じ結果=再現できる。試験の再作成や検証がやりやすい
余りが出た直後「余り、余り+1、余り+2…」を4で割った余りで、11問ぶんの位置をあらかじめ作る剰余演算による巡回。①②③④が均等に回る。正本の原則4「正解の位置を分散させる」の実装
設問ごとに1番目にある正答を抜き取り、決められた位置に差し込む配列操作(splice)。誤答どうしの相対順は保たれる
下線問題ができた直後下線部の文と正答から記号と空白を落とし、3文字ずつの破片に刻んで重なりを測る。72%以上なら「言い換えただけ」N-gram(3文字組)による類似度。意味は分からなくても、写しかどうかは測れる
同じ判定を、別の目的でも正答が8文字以上あるのに解説と重ならないなら、解説が別の選択肢の話をしているとみなして差し戻す整合性検査。1つの道具を逆向きに使い、AIの「解説と正答のずれ」を捕まえている
短い文の時8文字未満は判定しないなぜ:短い言葉はどうしても重なってしまうため誤検知(false positive)を避けるための下限。検査は厳しすぎても使えなくなる
たとえるなら トランプを混ぜるのに、サイコロではなく「箱に書かれた文字から配り方を決める」やり方。毎回同じ配り方になりますが、誰も手心を加えられません。
4章 出演者カタログ16

内部の直し係 AIを使う 復旧

validateWithRepairOnce / retryTransientOnce / lib/auto-repair.ts(44行)

44行しかない小さな担当ですが、このアプリの粘り強さのほぼ全部がここにあります。形式違反は「理由を添えてもう1回だけ作り直させる」、通信の一時不良は「700ミリ秒待って1回だけ送り直す」。どちらも1回だけです。

【形式が違う時】検査に落ちる ──▶ ⑬ ──[前回のJSON+落ちた理由]──▶ Claude
     ├─2回目が合格 → 何事もなかったように先へ(画面には出さない)
     └─2回目も不合格 → 「自動修正後も形式検証を通過できませんでした: …」
【通信が不調な時】408 / 425 / 429 / 500 / 502 / 503 / 504 / 529 か接続失敗
     └──▶ 700ミリ秒待つ ──▶ もう1回だけ送る(それでも駄目なら工程を失敗に)
受け取るもの不合格になった成果物と、その理由(検査が出したエラー文)
出すもの作り直した成果物(1回だけ)
AI使う。作り直しの時だけ、同じモデルをもう一度呼ぶ
失敗した時2回目も駄目なら、その工程を失敗にして人に返す
いつ動くか誰が・何を・なぜ(やさしく言うと)技術のことば(入り口)
検査に落ちた瞬間エラーの文言を、そのまま「何が駄目だったか」として使い回す例外メッセージの再利用。検査の言葉が、そのままAIへの指示の言葉になる。両方を1か所で保てる
作り直させる時前回のJSONを丸ごと同封し、「合格している所は維持し、違反箇所だけ直せ」と頼むなぜ:白紙から作り直させると、良かった部分まで変わるため差分修正の指示。全面再生成との違いは大きい。事実関係の揺れを抑えられる
2回目が合格した時ログに1行残すだけで、画面には何も出さない。利用者から見れば「1回で成功した」内部リトライ。成功した回復は、利用者に見せない。ただし記録には残す
2回目も落ちた時そこで諦め、工程ごと失敗にする再試行回数の上限。無限ループと費用の暴走を防ぐ。人が押す再試行(⑮)に引き継ぐ
通信が失敗した時相手が返した番号を見て、待てば直る種類かを判定する。混雑(429)や一時障害(5xx)だけ送り直す一時的エラー(transient error)の切り分け。入力ミス(400番台)は何度送っても直らないので送らない
送り直す前700ミリ秒だけ待つ。回数は1回だけで、待ち時間は増やさないバックオフ(間を置く)。ただし段階的に延ばす方式(指数バックオフ)は採っていない。11章
たとえるなら 提出物を突き返された時、白紙から書き直すのではなく赤字の入った箇所だけ直して出し直す人。ただし出し直しは1回まで。2回目も駄目なら、上司に相談します。
4章 出演者カタログ17

最終検品と組立係 AIを使わない 検査

assembleProblemSet / validateProblemSet / lib/workflow-ai.ts・lib/problem-schema.ts

9工程目。バラバラに作ってきた部品を1つの問題集に組み立て、十数項目の検査を通す担当。ここを通らなければ、保存も表示もされません。

⑦ ──[調査・設計図・設問2バッチ]──▶ ⑭ 組立 ──▶ 問題集1式(ProblemSet)
  検査:構造3〜5件/本文1つ/設問8問(4択7+論述1)/選択肢はどれも4つ/
        正解位置が⑫の並びと完全一致/職業への接続1問以上/新形式1問以上/
        論述はキーワード2語・観点3件/出典IDが実在/用語8〜12件/年表4〜10件/
        本文の空欄2・下線2/空欄下線を問う設問は本文を必ず1つ参照
    ├─全部通る ──▶ ⑮ 台帳へ保存(札を「完成」に)
    └─1つでも駄目 ──▶「最終検証に失敗しました: …」(先頭5件だけ見せる)
受け取るものここまでの途中経過すべて(調査・設計図・設問2バッチ)
出すもの完成した問題集1式(JSON)
AI使わない
失敗した時完成扱いにしない。途中経過は残るので、この工程だけ再試行できる
いつ動くか誰が・何を・なぜ(やさしく言うと)技術のことば(入り口)
組み立てる瞬間題名は設計図から、テーマ・期間は利用者の入力から、出典は調査結果から取る。AIの言い値をそのまま使わない項目がある正規化(canonicalize)。同じ情報が複数の出所にある時、信頼できる方で上書きする
同じ時難易度「B」、想定読者の文言、基準日=終了日をコード側で固定する定数化。毎回同じ値は、AIに書かせない。ぶれる余地を減らす
検査する時落ちた項目で止めず、全部集めてから返すなぜ:1つ直すたびに次が出る、を避けるためエラーの収集。1件目で例外を投げる作りとの違い。直す側の手間が桁違いに減る
設問を数える時8問・4択7問・論述1問。加えて「職業への接続」と「新形式」が1問以上あるか仕様の数値化。プロンプトで頼み(⑪)、機械で確かめる(⑭)の二段構え
出典を照合する時設問が指す出典IDが、実際の出典一覧に存在するか1つずつ確かめる参照整合性。存在しない根拠番号は、読み手からは検証できない。だから機械で弾く
同じ関数の中で設問13問の古い仕様を通す分岐が、いまも残っているデッドコードに近い分岐。いまの経路では13問は生成されない。11章の割り切りの1つ
たとえるなら 出荷前の検品台。ネジが1本足りなければ箱に詰めません。しかも「足りない物リスト」を、一度に全部出してくれます。
4章 出演者カタログ18

台帳係 AIを使わない 書く

completeStep / failStep / retryJob / listJobs / deleteJob / lib/job-repository.ts

工程が終わるたびに、成果・所要時間・失敗理由をまとめて1回で書き込む担当。この担当がいるから、途中で止まっても続きから再開できます。

⑤ ──[成果・所要時間]──▶ ⑮ 台帳係 ──[3つの書き込みを1回のまとまりで]──▶ D1
  ① job_steps に1行(工程名・何回目・秒数・成果のJSON)
  ② generation_jobs を更新(次の工程名・進んだ数・途中経過)
      ※ WHERE に「走行中で、工程がこのまま」を付ける=横取りされていたら書かない
  ③ 完成時のみ problem_sets に完成品(同じ依頼なら上書き)
  失敗時 ── 失敗も1行残して札を「要再試行」に / 同じ工程の失敗3回で打ち止め
受け取るもの依頼票、成果物、所要ミリ秒、(完成時は)問題集1式
出すもの更新された台帳。呼び出し側へは何も返さない
AI使わない
失敗した時「工程の保存に失敗しました。」中途半端には書かない
いつ動くか誰が・何を・なぜ(やさしく言うと)技術のことば(入り口)
工程が成功した瞬間3つの書き込みを1回のまとまりとして送る。なぜ:一部だけ書かれた状態を作らないためバッチ実行と原子性。すべて成功か、すべて失敗か。「工程は進んだのに成果が無い」を防ぐ
依頼票を更新する時「走行中で、工程がこのままなら」という条件を付けて書く。⑥で取った鍵を、書き戻す時にもう一度確かめている楽観ロックの再確認。実行中に時間切れ回収(⑥)が走った場合でも、上書きしない
次の工程名を決める時9工程の一覧を見て次の名前を取り、9つ目まで来ていたら札を「完成」にする状態遷移。順番はコードの配列1つで決まる。工程を足すのは、その配列に1行足すこと
進捗を出す時進んだ工程数 ÷ 9 を百分率に丸める。保存はしない導出値。計算で出せる値は保存しない。保存すると、ずれた時にどちらが正しいか分からなくなる
失敗した時失敗も1行として残す。何回目の挑戦だったかも記録する監査ログ。成功だけ記録しても原因は追えない。画面の「再試行 1」はこの記録から出ている
再試行を頼まれた時同じ工程の失敗が3回に達していたら断り、「入力を見直して新しいジョブを」と案内する再試行の上限。履歴は既定12件・最大50件まで。処理中の履歴は削除できない(409)
たとえるなら 工場の日報係。1工程ごとに、成果物・所要時間・不具合をまとめて一度に書きます。書きかけの日報は残しません。
4章 出演者カタログ19

表示係(固定レンダラー) AIを使わない 見せる

ProblemSetView / QuestionCard / MarkedText / app/components/problem-set-view.tsx

完成した問題集のJSONを、決まった形の画面に流し込むだけの担当。AIはHTMLを1文字も書きません。本文の目印を、押せる空欄や下線に変えるのもここです。

⑮ 台帳の完成品(JSON) ──▶ ⑯ 表示係
  [[BLANK:A|正答]]  → 押すと答えが出るボタン(もう一度押すと隠れる)
  [[UNDERLINE:a|文]] → (a) の付いた下線
  [[REF:a]]         → 設問の中の (a) ボタン ──押す──▶ 本文の該当箇所へ移動
  「解答・解説を見る」──▶ 正解に○、選んだ誤答に印、解説を開く
  年表・用語集・出典は折りたたみ / 解答の状態はメモリだけ(保存しない)
受け取るもの完成した問題集1式(JSON)
出すもの画面。印刷にも対応する
AI使わない
失敗した時完成品が無ければ、この部分ごと画面に出ない
いつ動くか誰が・何を・なぜ(やさしく言うと)技術のことば(入り口)
完成品を受け取った瞬間本文を目印で切り分け、普通の文・空欄・下線・参照の4種類に分けるパーサ(構文解析器)。同じ関数を、検査(⑩⑭)でも使い回している=ずれが起きない
空欄を描く時記号(A・B)だけを出し、押されたら中の正答に入れ替える開閉状態(トグル)。押した記録はメモリだけ。localStorage は使っていない
設問の (a) を押した時折りたたまれた本文を先に開いてから、該当箇所へ滑らかに移動して焦点を当てるscrollIntoViewrequestAnimationFrame開く前に動かすと位置がずれるため、1フレーム待つ
「解答・解説を見る」を押した時正解に○を付け、選んでいた誤答に印を付け、解説を開く。上部には一括で開閉するボタンもある採点はブラウザの中だけで完結。答え合わせのためにサーバーへ行かない
画面の幅が960ピクセル以下になった時2段組みをやめ、本文を折りたたみパネル(高さ44vh)にする。広い時は本文が左に貼り付いて動かないレスポンシブ(メディアクエリ)。正本プロンプトの指定は860pxで、実装は960px。ずれている(11章)
印刷する時見出し・入力欄・ボタンを消し、本文を開き、設問がページ間で割れないようにする@media printbreak-inside: avoidPDF書き出しの機能を作らず、印刷用の見た目だけ用意している
たとえるなら 印刷所の版。原稿(JSON)が変わっても、版(レイアウト)は変わりません。原稿の書式が違っていれば、刷る前に突き返されます。
4章 出演者カタログ20

5章

1回の流れを、時間軸で追う

ボタンを押してから問題集が出るまで、実際に何が起きているか。「次へ進めて」の電話が9回かかる、というのがこの表の骨格です。

工程名(画面の表示)動く担当AIこの回に保存されるもの
依頼票をつくる
(POST /api/jobs)
①→③依頼票1行(番号・テーマ・期間・モデル・札は「待ち」)
1調査計画を作成あり調査計画(見立て+調査単位3件)
2情報収集 1/3あり+検索証拠パック1本+出典(最大8件)
3情報収集 2/3あり+検索証拠パック1本+出典
4情報収集 3/3あり+検索証拠パック1本+出典(ここで合計5件未満だと失敗)
5問題の設計図を作成あり設計図(題名・構造4・本文1・用語8・年表6)
6問題 1〜4を作成⑪→⑫あり設問4問(正解位置は⑫が配り直した後の形)
7問題 5〜8を作成⑪→⑫あり設問4問
8問題形式を確認なし数えた結果だけ(2バッチ・8問そろっているか)
9検証して保存なし完成した問題集1式/札が「完成」に変わる

その1回の中で、毎回必ず起きていること

  1. ②が advance を叩く → ⑥が条件付きの1行書き換えで鍵を取る(取れなければ409で引き返す)。
  2. ⑦が工程名を見て担当を選ぶ。AIを呼ぶ工程なら、返す形をJSONスキーマで固定して頼む。
  3. 返ってきたものを機械が検査する。落ちたら⑬が1回だけ作り直させる。通信の不調なら700ミリ秒待って1回だけ送り直す。
  4. ⑮が「工程の記録1行+依頼票の更新(+完成時は問題集)」をまとめて1回で書く。
  5. ②に更新後の状態が返る。札が「走行中」のままなら2.5秒待って見に行く。「完成」か「要再試行」になるまで繰り返す。

ここから読み取れること

工程の区切りは、そのまま「やり直せる地点」の区切りです。9つに割ったのは進捗を見せるためだけではありません。1回のAI呼び出しが失敗した時に、そこまでの調査結果や設計図を捨てずに済むようにするためです。もし「1回のお願いで全部作る」形なら、8問目で形式違反が見つかった時、調査からやり直しになります。割り方=失敗の巻き戻し幅、という設計判断がここにあります。

時事問題生成 のしくみ21

6章

AIを使っている所と、使っていない所

「AIアプリ」と一口に言っても、どこまでAIに任せているかは大きく違います。このアプリの線引きは、はっきりしています。

AIを呼ぶのは、16人中5人。1回の生成で7回だけ

担当呼ぶ回数上限トークン何を任せているか
⑧ 調査計画係1回2,000テーマを重ならない3つの調査単位に割る(判断
⑨ 情報収集係3回5,000 ×3ウェブを検索し、日本語の証拠と出典にまとめる(調査と要約
⑩ 設計図係1回9,000本文・構造・用語・年表を書く(文章作成
⑪ 設問の作成係2回9,000 ×2設問文・選択肢・解説を書く(文章作成
⑬ 内部の直し係0〜7回同上形式違反があった時だけ、同じ担当をもう1回呼び直す

ウェブ検索が走るのは最大9回(3工程 × 各3回まで)。使うモデルは claude-sonnet-5(既定)か claude-opus-5 で、利用者が生成のたびに選びます。両方とも同じ工程を通ります——モデルを変えても、工程の数も検査の厳しさも一切変わりません。

AIを使っていない所(=「賢く判断している」わけではない所)

つい「AIがやっている」と思いがちな所実際にやっていること
正解が①②③④に散らばっているテーマの文字コードの合計を4で割った余りから並びを決めている。乱数ですらない(⑫)
「下線部の言い換えでしかない設問」を弾く文字を3つずつに刻んで、重なりが72%以上かを数えている(⑫)
設問と本文の空欄がきちんと対応している目印の並び順を正規表現で数えている(⑩)
問題数や用語数が毎回きっちり揃うコードに数字が書いてあり、合わなければ差し戻す(⑭)
途中で失敗しても続きから再開できる工程ごとにデータベースへ保存している(⑮)だけ
画面のレイアウトが毎回同じAIはHTMLを書いていない。人が書いた固定の型に流し込んでいる(⑯)

ここから読み取れること

このアプリは、AIに「文章を書く」と「調べる」だけを任せ、数・順番・位置・整合性は全部プログラムで確かめています。生成AIは、同じ頼み方をしても毎回違うものを返します。だから「毎回そろっていてほしいもの」は、頼むのではなく機械で数える。これはこのアプリに限らず、AIを業務に組み込むときの一般的な作法です。逆にいえば、数えられない品質(面白いか、本当に重要な論点か)は、まだ人が読んで判断するしかありません。

時事問題生成 のしくみ22

7章

間違いを防ぐ、7つの工夫

どれも派手ではありませんが、この7つが無いと「たまに壊れるアプリ」になります。

1 同じ検査を、画面とサーバーの両方でやる(①③)

画面の検査は速さのため、サーバーの検査は守りのため。窓口は直接叩けるので、画面を通らない依頼が来ても壊れないようにしてあります。片方だけでは足りません。

2 鍵は「確かめてから取る」のではなく「条件付きで書いて、結果を見る」(⑥)

「空いているか確かめる → 取る」の間には、必ず隙間があります。その隙間で他の人に取られる。だから確認と取得を1文にまとめ、「1行だけ変わった」ことをもって勝者とします。データベースが1行ずつの書き換えを保証してくれることを利用した、古典的で確実な方法です。

3 書き込みは「まとめて1回」(⑮)

工程の記録・依頼票の更新・完成品の保存を別々に3回送ると、2回目で失敗した時に「工程は進んだのに成果が無い」という矛盾が残ります。1つのまとまりにすれば、全部書かれるか、1つも書かれないかのどちらかになります。

4 直しは1回、再送も1回(⑬)

粘れば直るものは粘り、直らないものは早く諦める。上限を決めておかないと、費用と時間が青天井になります。混雑(429)や一時障害(5xx)だけ送り直し、入力ミス(400番台)は送り直さない、という切り分けもここに入っています。

5 「返ってくる形」を先に決めてから頼む(⑧⑩⑪)

AIに自由に書かせて後から読み取るのではなく、JSONの設計図を渡して、その形でしか返せなくする。書式の崩れを、そもそも起こさない作りです。それでも件数や順番までは保証されないので、6と7で確かめます。

6 同じ検査コードを、作る時と最後の両方で使う(⑩⑭⑯)

本文の目印を読み解く関数は1つしかなく、設計図の検査・最終検品・画面表示の3か所すべてが同じ関数を呼んでいます。だから「検査は通ったのに表示で崩れる」が起きません。

7 止まった仕事を、読むついでに片付ける(⑥)

走行中のまま4分動かない依頼票を、台帳を読むたびに「要再試行」へ落とします。見張り役を常駐させると、それ自体が壊れます。ふだんの動作のついでに掃除するのは、常時動くものを増やさないための工夫です。

ここから読み取れること

7つのうち6つは、AIとは無関係な普通のプログラムの作法です。AIを使うアプリだからといって、特別な守りが要るわけではありません。むしろ「返事が毎回違う」「時間がかかる」「途中で止まる」という3つの厄介さに対して、昔からある手当てをきちんと当てているかが効いてきます。

時事問題生成 のしくみ23

8章

データは、どこに、どんな形で置かれているか

置き場所は1つだけ ― Cloudflare D1(表3つ)

表の名前1行の意味主な中身
generation_jobs依頼票1件番号・テーマ・開始日・終了日・使うモデル・札・今の工程・進んだ数・途中経過(state_json)・完成品・エラー文・3つの時刻
job_steps工程の実行1回依頼番号・工程名・何回目の挑戦か・成功か失敗か・使ったモデル・成果のJSON・所要ミリ秒・エラー文
problem_sets完成した問題集1式依頼番号・題名・テーマ・期間・問題集まるごとのJSON(依頼1件につき1行だけ)

索引は6本。job_steps には「依頼・工程・試行回数」の組で重複を許さない索引があり、同じ試行が二重に記録されないようになっています。

正本(本物)はどれか

外に出ていくもの・外から入ってくるもの

相手やり取りする中身
Anthropic(AIの会社)出る:テーマ、期間、正本プロンプト27,787字、調査結果、前回のJSON(直しの時)。入る:計画・証拠・設計図・設問のJSON
ウェブ(検索)AI側の道具として実行される。アプリが直接どこかのサイトを取りに行くことはない
ブラウザ画面と、依頼票・完成品のJSON

APIキーはコードに1文字も書かれていません。動く場所から渡される設定(env.ANTHROPIC_API_KEY)を読み、無ければ手元の環境変数を見る、という順に探します。手元で動かす時は、1つ上の階層の共通 .env を読み込みます。

ここから読み取れること

「途中経過を1つのJSONの箱に入れて、依頼票の1行に持たせる」のが、このアプリの背骨です。工程ごとに専用の表を作らなかったので、工程を足したり中身を変えたりしても、表の設計を変えずに済みます。代わりに中身の形はデータベースからは見えません――何が入っているかを保証しているのは、表の定義ではなくコードの型と検査だけ、という割り切りです。

時事問題生成 のしくみ24

9章

どこで動き、外からはどう使えるのか

動いている場所

実体
公開先jiji-mondai-opus.akuramochi.chatgpt.site(OpenAI の Sites 基盤)
サインインの壁アプリの外側。基盤が「ChatGPTでサインイン」の画面を出している。アプリのコードには認証の処理が1行もない
サーバー本体Cloudflare Worker 1つ(worker/index.ts)。呼ばれた時だけ動き、返事を返すと消える
画面の枠組みReact 19 + vinext(Next.js風の書き方を、Vite で Worker 用に組み直す道具)
データベースCloudflare D1(中身はSQLite)。バインディング名は DB
手元で動かす時npm run devhttp://localhost:3023。共通の .env からAPIキーを読む

外から叩ける窓口(全6つ)

方法住所役目
GET/api/jobs履歴の一覧(既定12件・最大50件、新しい順)
POST/api/jobs依頼票を作る(202)。テーマ・期間・モデルを渡す
GET/api/jobs/{id}1件の状態・工程の記録・完成品を読む
DELETE/api/jobs/{id}履歴を消す(処理中は409で断る。3つの表からまとめて消す)
POST/api/jobs/{id}/advance1工程だけ進める。②が9回叩く窓口
POST/api/jobs/{id}/retry失敗した工程を「待ち」に戻す(同じ工程で3回失敗していたら断る)

使われていない窓口が、2つ残っている

/api/research/api/generate の2つは、画面から一度も呼ばれていません。これは旧方式(1回のお願いで13問まとめて作る)の名残で、中身も現在の仕様(8問)と食い違ったままです。使うモデルも claude-opus-5 に固定されています。

ただし窓口としては生きており、外から直接叩けば動きます。サインインの壁の内側なので誰でもというわけではありませんが、「消し忘れた扉」がある状態です。11章に挙げた片付け候補の筆頭です。

ここから読み取れること

守りを担っているのはコードではなく、置き場所です。アプリ自身は「誰が使っているか」を一切知りません。だからこのコードを別の場所へ引っ越すと、同じコードのまま無防備になります。個人用の道具として成立している前提が、置き場所に乗っている――という理解が要ります。

時事問題生成 のしくみ25

10章

品質を保つ仕組み ― あるものと、無いもの

あるもの

仕組み何を守っているか
型(TypeScript)「問題集にはこういう項目がある」をコードの段階で固定する。lib/types.ts がその定義。書き間違いは、動かす前に見つかる
JSONスキーマAIの返す形を、こちらから指定して固定する。3か所(計画・設計図・設問)で使用。余計な項目も禁じている
件数と順番の検査⑩⑫⑭。プロンプトで頼み、機械で数える二段構え。スキーマでは守れない「4件ちょうど」「A→a→B→b」を担保する
自動修正と自動再送⑬。落ちた理由を添えて1回だけ作り直させる/700ミリ秒待って1回だけ送り直す
工程ごとの記録⑮。成功も失敗も所要時間つきで残る。どの工程が重いか・どこで落ちやすいかが、推測ではなく記録で分かる
ESLint書き方の乱れと、よくある間違いを機械的に指摘する(npm run lint

無いもの ― 自動テストは、事実上1本だけ

tests/ というフォルダはです。動くテストは scripts/test-auto-repair.mjs の1本だけで、npm run test:repair で実行します。中身は次の3つを確かめるものです。

package.jsontest というコマンドはありません(test:repair のみ)。つまり⑬以外の担当には、自動テストが1つも無い状態です。⑫の正解位置の計算や言い換え判定、⑩の目印の並び順検査、⑮のまとめ書き込みは、いずれも入出力がはっきりしていてテストしやすいのに、まだ書かれていません。

ここから読み取れること

品質の重心が「実行時に機械が確かめる」側に大きく寄っています。壊れたものは実行時に必ず止まるので、間違った教材が世に出ることはありません。その代わり、コードを直した時に「前は動いていた所」が壊れていないかを、自動で確かめる手段がありません。個人用の道具としては十分成立しますが、手を入れる頻度が上がると、まずここが痛みます。テストを足すなら、AIを呼ばずに動く⑫(正解位置・言い換え判定)が最も費用対効果の高い出発点です。

時事問題生成 のしくみ26

11章

弱いところと、これから選べる道

欠陥という意味ではなく、「今はこう割り切ってある」という設計上の選択と、その代償を並べます。

作りの上での割り切り

もし次に手を入れるなら(優先度順の一案)

やること効き目と手間
1使われていない2つの窓口を消すlib/anthropic.tslib/prompts.ts・13問の分岐も一緒に)読む人の混乱と、外から叩ける扉を同時に減らせる。消すだけなので手間は小さい
2⑫と⑩に自動テストを足す(正解位置の計算・言い換え判定・目印の並び順)AIを呼ばずに動く部分なので、テストが速くて安い。直した時の安心感が一番増える
3進行役をサーバー側へ移す(画面を閉じても最後まで進むようにする)「見ていないと終わらない」の解消。ただし作りは一段複雑になる
4再送を段階的に延ばす(700ミリ秒 → 2秒 → 5秒)混雑時の成功率が上がる。⑬の数行を直すだけ

あえてやらないほうがよいこと

検査を緩めて「とりあえず完成にする」こと。いまは1項目でも合わなければ完成扱いにしません。厳しく見えますが、この教材の値打ちは「出典があり、形式がそろっていること」そのものです。ここを緩めると、AIが書いたそれらしい文章が、検証されないまま「問題集」として残ります。緩めるくらいなら、失敗を人に見せて、やり直させるほうが筋が通っています

時事問題生成 のしくみ27

付録

この資料の言葉と、実際のファイルの対応

実物を開いて確かめたい時のための対応表です。すべて C:\Users\aky_m\Scripts\current-affairs-mvp\ の下にあります。

この資料での呼び名実際の名前場所
① 受付カウンターStudioClient / submitapp/studio-client.tsx
② 進行役driveJob / loadHistory / openJobapp/studio-client.tsx
③ 入力の門番と整理券発行parseGenerationInput / parseWorkflowModel / createJoblib/request.ts / app/api/jobs/route.ts
④ 保存庫の管理人getDb / ensureSchema / SCHEMA_SQLdb/index.ts(+ db/schema.ts, drizzle/)
⑤ 現場監督POST ハンドラapp/api/jobs/[id]/advance/route.ts
⑥ 鍵預かり係acquireStep / recoverStaleJoblib/job-repository.ts
⑦ 工程の割り振り係executeWorkflowStep / WORKFLOW_STEPSlib/workflow-runner.ts / lib/workflow-shared.ts
⑧ 調査計画係createResearchPlanlib/workflow-ai.ts
⑨ 情報収集係researchBatch / mergeResearch / parseResearchlib/workflow-ai.ts
⑩ 設計図係createBlueprint / validateBlueprintMarkup / passageMarkslib/workflow-ai.ts / lib/passage-markup.ts
⑪ 設問の作成係createQuestionBatch / QUESTION_SPECSlib/workflow-ai.ts
⑫ 公平さの検査係answerPattern / positionCorrectOption / isNearParaphraselib/problem-schema.ts / lib/question-quality.ts
⑬ 内部の直し係validateWithRepairOnce / retryTransientOncelib/auto-repair.ts
⑭ 最終検品と組立係assembleProblemSet / validateProblemSetlib/workflow-ai.ts / lib/problem-schema.ts
⑮ 台帳係completeStep / failStep / retryJob / listJobs / deleteJoblib/job-repository.ts
⑯ 表示係ProblemSetView / QuestionCard / MarkedTextapp/components/problem-set-view.tsx
作問の正本(27,787字)時事演習問題 作成プロンプト v3.0docs/prompt-v3.md
データの形の定義型定義lib/types.ts / lib/workflow-shared.ts
貸倉庫の入口worker.fetchworker/index.ts
見た目(配色・レイアウト)スタイル 1,393行app/globals.css
唯一の自動テストnpm run test:repairscripts/test-auto-repair.mjs(tests/ は空)
使われていない旧経路researchTopic / generateProblemSetlib/anthropic.ts / app/api/research・generate/route.ts

決め打ちされている数値の一覧(すべてコードから取った実値)

工程の数9AIの呼び出し7回ウェブ検索最大3回×3工程
設問8問(4択7・論述1)押さえる構造4件用語集 / 年表8件 / 6件
本文の字数700〜900字解説の字数140〜220字論述80字・語2・観点3
テーマの長さ3〜400字出典(1工程)最大8件・2件未満で失敗出典(統合後)最大20件・5件未満で失敗
上限トークン2,000 / 5,000 / 9,000引用文の長さ700字で切る1問の出典最大3件
自動修正1回だけ自動再送700ミリ秒後に1回人が押す再試行同じ工程で3回まで
進捗の見に行き方2,500ミリ秒ごと時間切れの回収4分履歴既定12件・最大50件
言い換えの判定3文字組の重なり72%以上判定の下限8文字未満は判定しない画面の折り返し960px / 680px
時事問題生成 のしくみ28

最後に

3つだけ持ち帰るなら

1 長い仕事は、割って、1区切りごとに保存する

このアプリの一番の判断は、「1回のお願いで問題集を作る」のをやめて9つに割ったことです。割った理由は進捗を見せるためではなく、失敗したときに巻き戻る幅を小さくするため。8問目で形式違反が出ても、調査からやり直しにはなりません。AIに限らず、時間のかかる処理を人に頼む時・機械に頼む時の、そのまま使える型です。「どこまで戻ってやり直すことになるか」を先に決める、と言い換えられます。

2 AIには「書くこと」を頼み、「数えること」は頼まない

問題数8問、構造4件、正解位置の散らばり、出典番号の実在――毎回そろっていてほしいものは、全部プログラムが数えています。AIは同じ頼み方でも毎回違うものを返すので、「お願いしたから大丈夫」は成り立ちません。逆に、頼んで確かめられるものは頼めばいい。「これは頼むことか、数えることか」で切り分ける――AIを仕事に入れる時の、実務的な判断軸そのものです。

3 守りは、たいてい「置き場所」に乗っている

このアプリには認証のコードが1行もありません。サインインの壁は配信基盤が出しており、同じコードを別の場所に置いた瞬間、守りは消えます。APIキーも同じで、コードには書かれておらず、動く場所から渡されます。「このアプリは安全か」という問いは、コードだけを見ても答えが出ない。何がコードの責任で、何が置き場所の責任なのか――外注するとき・引き継ぐときに、まず確かめるべき境目です。

※ この資料は、2026年8月11日時点の current-affairs-mvp のプログラムを実際に読んで作成しました。
※ 「担当者」という言い方はこの資料の説明のための呼び名で、プログラム内にその名前が付いているわけではありません。対応は付録の表のとおりです。
※ 数値(上限、待ち時間、字数、しきい値)はすべて実際のコードから取った値です。プログラムを直せば、これらの数値も変わります。
※ サインインの壁の挙動だけは、コードではなく公開URLの応答から確認したものです。

時事問題生成 のしくみ29