← ガイダンス一覧へ戻る

6ABC Note候補の絞り込みと下読み

Zettelkasten Inbox / ブロック詳細

選んだTagの配下だけを候補にし、題名で9件まで削ってから、その9件の本文をサーバーが下読みして並べ直す。安い材料で削って高い材料で決める、という二段構えがそのまま形になっている。

抽象度 Lv.4(技術用語+システム上のやりとりまで/製品名・ライブラリ名は書かない)

1. 概要

Tagが決まったあと、その配下のABC Note(知識ページ)のうちどれに書き足すかを選ばせる場所。 ABC Noteは題名だけでは中身が分からないので、候補の本文を先に読んで、 「主な章」「論点」の形で見せるのがこのブロックの仕事。

絞り込みは2段階。まずTagで絞り、次に題名だけで仮の順位を付けて上位9件にする。 その9件だけサーバーが本文を下読みし、返ってきた中身でもう一度並べ直す「全部の本文を読んでから選ぶ」のは重すぎるので、2回に分けて絞っている。

2. 位置づけ

5. Tagの推薦と新規作成 → 6. ABC Note候補の絞り込みと下読み → 7. 画像の添付/8. 保存

3. 処理内容(Lv.4)

4. なぜこの位置にあるか(設計意図)

Tagの後ろでなければ成立しない。候補は「選択中のTagの配下」に限られる。 Tagが決まっていなければ候補は空になる。この順番が、このアプリの操作の骨格そのもの。

下読みを9件に絞っている理由。1件ごとにNotionへ1往復かかる。 Tag配下に50件あれば50往復で、待ち時間もNotionの回数制限も現実的でない。 題名という安い材料で先に9件まで削ってから、高い材料(本文)を使うという二段構え。

「親Tagがちょうど1つ」という厳しい条件。親Tagが0個や2個以上のABC Noteは、 ここでは候補に出ない(全体マップでは「要確認」として別枠に出る)。 どのTagの配下かが一意でないと、絞り込みの理屈が崩れるため。 8番の保存でも同じ条件を再検査していて、この前提はアプリ全体で守られている。

控えを6時間と長く取れる理由。期限だけでなく 「そのページの最終更新時刻」を鍵にしているので、 Notion側で書き換えられれば時間に関係なく無効になる。だから長くても古くならない。

5. 詳細プロセス分解

工程主体やりとりの内容方向
6-1ブラウザ(内部)唯一の親が選択Tagのものだけに候補を限定
6-2ブラウザ(内部)題名だけの仮の点数付けと上位9件の選出
6-3ブラウザ → 下読みAPI候補9件までのID
6-4下読みAPI → Notion各ABC Noteの先頭100ブロック(3件ずつ)
6-5下読みAPI(内部)章・論点の抽出と照合用本文の切り出し
6-6下読みAPI → ブラウザ説明文・照合用本文・打ち切りの有無
6-7ブラウザ(内部)本文の一致を加えた並べ直し

各行を文章にすると

6-1 まず、選んだTagにぶら下がっているABC Noteだけを残す。 Tagを2つ選べば、どちらかにぶら下がるものが候補になる。

技術的には 2番が返した一覧の中から、 親Tagの数がちょうど1つ、かつそのIDが選択中の集合に含まれるものを残す。 「ちょうど1つ」という条件が厳しく、親Tagを2つ付けたABC Noteは 受信箱の候補には一切出てこない。Tagを外すと、そのTag配下だったABC Noteは 選択からも自動で外れる(画面の状態が矛盾しないようにするため)。 「新しく作る」を選んだときの親Tagも、選択中のTagから外れたら自動で付け替わる。

6-2 残った候補に、題名だけを見た仮の順位を付け、上から9件に絞る。 すでに選んであるものは、順位に関係なく残す。

技術的には 点数は 選択Tagとの一致数×120(Tagを複数選んだとき、どのTagの配下かで差が付く)、 題名がそのまま本文に含まれれば120点、題名の重なり×70、 引かれた回数の常用対数×2。この時点では本文の点は0。 上位9件と選択済みのものを混ぜ、IDで重複を除いた集合が候補になる。 9件という数はサーバー側の上限(1回1〜9件)と一致している。

6-3 まだ中身を読んでいない候補があれば、そのIDをまとめてサーバーへ送る。 一度読んだものは、もう送らない。

技術的には POST /api/candidate-contentapplication/json{"permanentIds": [...]}。 サーバー側は1〜9件・全部がNotionのIDの形を確認し、外れたら 400。 そのあとハイフンの位置を正規化して重複を除く。 ブラウザ側は「まだ持っていないID」だけを送り、切り替えが起きたら取り消す。

6-4 サーバーは候補1件ずつ、そのページの中身を読みに行く。 ただし全部を一気にではなく、3件読んだら0.9秒待つ

技術的には GET /v1/blocks/{id}/children?page_size=100続きの目印があっても追いかけない――先頭100ブロックだけ見る。 3件を同時に投げ、揃ったら次の3件へ進む前に0.9秒待つ。 これはNotionの1秒あたりの要求数の制限(おおむね毎秒3回)に合わせた意図的な間引きで、 9件なら「3件・待つ・3件・待つ・3件」で約1.8秒の待ちが入る。 2番が3つの取得を単純に直列にしていたのに対し、 ここは「同時に3つ、そのあと休む」という別の作戦を取っている。

6-5 読んだ中身から、章立てと論点を拾って1〜2行の説明文にする。 見出しが無いページなら、最初の段落をそのまま使う。

技術的には ブロックの種類で振り分ける。 大見出し(heading_1)=章を最大3つ(各46文字まで)、 中見出し(heading_2)=論点を最大2〜3つ(各62文字まで)。 12文字以上の段落も拾う。ただし 「その他」という見出しと、記号だけの行(- | など)は捨てる―― 「その他」は8番が転記に使う定型の見出しなので、説明文に出しても情報にならないため。 URLは事前に全部取り除く(4番と同じ理由で、長いURLが照合を歪めるから)。 説明文は全体で220文字まで、照合用の本文は6,000文字まで。 見出しも段落も無ければ「本文はまだありません。関連Literature Note ◯件。」と出す。 結果は6時間・最大500件の控えに入り、鍵はそのページの最終更新時刻。 500件を超えたら古いものから順に捨てる。 失敗した場合は控えから外すので、次に呼べばやり直せる。

6-6 説明文と照合用の本文を返す。10分間はブラウザ側にも置いてよい、と伝える。

技術的には 200private, max-age=300(本人のブラウザにだけ5分)。 中身は候補ごとに、説明文・照合用本文・最終更新時刻・ 本文から取れたのか題名だけなのかの区別・打ち切りの有無。 「題名だけ」の場合、次の並べ直しで本文の点が入らないことが説明できるようになっている。

6-7 説明文が届いたら、候補の順番を付け直す。 ここで初めて本文の中身が順位に効く

技術的には 6-2 の点数に 本文の重なり×115 を足して並べ替える。 この115という重みは、題名の重なり(×70)より重く、Tag一致(×120)に迫る大きさ。 つまり「題名は違うが中身が近い」ABC Noteが、 下読みのあとで上位へ跳ね上がることがある。これがこのブロックを作った目的そのもの。 検索窓に入力したときは推薦を止め、題名と説明文に対する単純な絞り込み(上位9件)に切り替わる。

ワークフロー図

ブラウザ
下読みAPI
Notion API
6-1 唯一の親が選択Tagのものだけに絞る
6-2 題名だけで仮の点数を付け上位9件に
6-3 POST /api/candidate-content(1〜9件)
6-4 各ABCの先頭100ブロック(3件ずつ)
6-5 章・論点を組み立て6,000字を残す
6-6 200 + private, max-age=300
6-7 本文の一致を足して並べ直す

6. この分解から見えること

  • 「安い材料で削って、高い材料で決める」が明確に実装されている。 題名は手元にあるのでタダ。本文は1件1往復かかるので高い。 9件まで削ってから本文を読む、という線引きが、 画面の待ち時間とNotionの回数制限の両方を睨んで引かれている。
  • 並べ替えが2回起きるので、候補の順番が目の前で入れ替わる。 最初に題名順で出て、1〜2秒後に本文の点が入って並び直す。 利用者から見ると「勝手に順番が変わった」ように見える作りで、 そのことを説明する表示は無い。
  • Notionの回数制限への対処が、ブロックごとに違う。 2番は直列、6番は「3件同時+0.9秒待ち」、10番は3つ同時。 共通の仕組みとして切り出されておらず、その場ごとに書かれている。 将来ここを変えるときは3か所を直すことになる。
  • 先頭100ブロックしか読まない割り切り。育ったABC Noteは 100ブロックを軽く超える。後半に書かれた内容は下読みに反映されないので、 長いABC Noteほど推薦が当たりにくくなる。打ち切ったことは応答に印として入っているが、 画面には出ていない。
  • 控えの鍵が「最終更新時刻」なのが上手い。 期限だけなら6時間は長すぎるが、Notion側で1文字でも直せばその瞬間に無効になる。 期限は「変わっていないものをいつまで信じるか」の上限として機能している。
  • 親Tagが1つでないABC Noteは、受信箱から永久に見えない。 10番の全体マップだけが「要確認」として拾ってくれる。 2つの画面が、この1点でつながっている。

7. 失敗時の挙動

  • 件数が0または10件以上:400 + 「ABC Note候補は1回につき1〜9件を指定してください。」
  • 画面が古く、Notionに無いIDが混ざっている:400 + 「ABC DBにない候補が含まれています。画面を更新してください。」
  • Notionが混んでいる(429):最大2回まで自動で待って再送。 それでも駄目なら 429 がそのまま返り、候補一覧の上に赤い文字で出る。 このとき候補自体は消えない――説明文が付かないまま、 題名だけの順位で選び続けられる。
  • 一部だけ失敗:3件ずつの束のどれか1件が失敗すると、 その束全体が失敗になる。失敗した分は控えから外れるので、次の操作でやり直される。
  • どこまで進んでから失敗すると何が残るか:読み取りのみなのでNotionには何も残らない。 画面は説明文なしで動き続ける。この失敗は保存(8番)を妨げない。

8. 未確認事項

  • 0.9秒という待ち時間と「3件ずつ」の根拠(Notionの制限値に対する見当と思われるが、コードに説明は無い)
  • 先頭100ブロックの打ち切りを画面に出さないのが意図か抜けか
  • 本文の重み115が、実際の使用感から調整されたものか
  • 控えの上限500件を超える規模で使われたことがあるか
  • 候補の並びが下読み後に入れ替わることを、利用者がどう受け取っているか

※ 2026年8月14〜15日時点の zettelkasten-inbox のプログラムを読んで作成しました。過去の設計資料は参照していません。

※ 番号は全体図・この解説・シーケンス図で共通です。工程番号は「ブロック番号-連番」で書いています。

全体図へ戻る / ブロック目次へ

akuramochi1.com — personal tools for learning and thinking.