Skip to content

技術書っぽさ検索機能の実装 - #91

Merged
Ojoxux merged 7 commits into
stagingfrom
codex/tech-book-search-reranking
Jul 22, 2026
Merged

技術書っぽさ検索機能の実装#91
Ojoxux merged 7 commits into
stagingfrom
codex/tech-book-search-reranking

Conversation

@Ojoxux

@Ojoxux Ojoxux commented Jun 29, 2026

Copy link
Copy Markdown
Owner

概要

  • 楽天ブックス検索結果に技術書らしさを表す techScore を付与し、検索 API の返却順を再ランキングするように変更
  • ISBN 完全一致、技術系出版社、タイトル・説明文・著者の技術キーワード、非技術書キーワード、ページ数、出版日の鮮度を ScoreRule として評価
  • debug=1 / debug=true のときだけ scoreReasons を返し、スコア調整時に理由を確認できるようにした
  • 技術書スコアリングのユニットテストと検索 API の結合テストを追加

確認

  • format 通過
  • typecheck 通過
  • test 通過

補足

  • ローカルの gh 認証トークンが無効だったため、PR 作成は GitHub コネクタ経由で行っています。
  • 作業外の未追跡ファイル(.codex/, AGENTS.md, SECURITY_REVIEW.md など)は PR に含めていません。

@Ojoxux

Ojoxux commented Jun 29, 2026

Copy link
Copy Markdown
Owner Author

@cursoragent

コードレビューしてください

@cursor

cursor Bot commented Jun 29, 2026

Copy link
Copy Markdown

コードレビュー: 技術書検索の再ランキング

全体として、スコアリングをドメイン層に切り出し、ルールベースで拡張しやすい設計になっています。ユニットテスト・結合テストもあり、Go の部分一致を避ける ASCII キーワード処理など、意図が明確な実装です。

一方で、日本語の技術書タイトルに対するネガティブキーワードの誤判定は、本 PR の目的と矛盾するため、マージ前に対応した方がよいです。


良い点

1. 責務分離が明確

tech-book-search.ts にスコアリングを集約し、SCORE 定数・ScoreRuleScoreReason でルールを表現しています。重み調整やルール追加がしやすい構造です。

2. API 統合が最小限

  const sortedResults = rankTechBooks(results, query, {
    includeReasons: debug === "1" || debug === "true",
  });

楽天 API 取得後に再ランキングするだけで、既存フローを壊しにくいです。scoreReasonsdebug 指定時のみ返す設計も妥当です。

3. ASCII キーワードの境界マッチ

function hasKeyword(text: string, keyword: string): boolean {
  if (!text || !keyword) return false;

  const normalizedKeyword = keyword.toLowerCase();

  if (isAsciiKeyword(normalizedKeyword)) {
    return new RegExp(`(^|[^a-z0-9])${escapeRegExp(normalizedKeyword)}([^a-z0-9]|$)`).test(
      text.toLowerCase(),
    );

Google 内の Go にマッチしないテストがあり、実運用で起きやすい誤検知を意識しています。

4. テストカバレッジ

  • 出版社・ISBN・ネガティブキーワード・出版日の鮮度
  • 検索 API の認証・ソート・debug=1

14 件すべてパスを確認しました。


要修正(マージ前に対応推奨)

1. ネガティブキーワードが技術書を不当に減点する

const NEGATIVE_KEYWORDS = [
  "漫画",
  ...
  "攻略本",
  "ゲーム攻略",
  "雑誌",
  "ムック",
];
タイトル例 加点 減点 結果
Reactムック +15 (React) -40 (ムック) 技術書なのに大幅減点
アルゴリズム攻略本 +15 (アルゴリズム) -40 (攻略本) 同上

日本の技術書では「○○ムック」「○○攻略本」はよく使われます。PR の目的(技術書を上位に)と真っ向から矛盾します。

提案: ムック / 攻略本 を削除するか、ゲーム攻略 のように文脈付きに限定する。回帰テストを追加する。


改善推奨(マージ後でも可)

2. レイヤー依存の向き

import type { BookSearchResult } from "../services/rakuten";

ドメインがサービス層に依存しています。BookSearchResultrakuten.ts / types/api.ts / フロントで重複定義もあります。

提案: 共通型を app/types/ に置き、サービス・ドメイン双方から参照する。

3. リクエストごとの RegExp 生成

ASCII キーワードごとに new RegExp(...) しています。キーワード約 80 個 × フィールド 4 つ × 最大 10 件で、1 リクエストあたり数百回のコンパイルになり得ます。

提案: モジュールロード時に Map<string, RegExp> で事前コンパイル。

4. キーワードの無制限加算

マッチするキーワードごとに満点加算されます。例: タイトルに JavaScript TypeScript React Vue Angular が並ぶと 15 × 5 = 75 点。

提案: フィールドあたりの上限(例: タイトルは最大 3 件)や逓減を検討。

5. 日本語キーワードの部分一致

設計 Web AI などは単純な includes です。インテリア設計入門 のような非技術書にも加点し得ます。楽天のクエリ絞り込みで緩和されますが、汎用クエリではノイズになります。

6. ページ単位の再ランキングと UI の不整合

BookSearchForm は「もっと読み込む」でページ結果を単純に連結します。

        setResults((prev) => [...prev, ...data.data!.results]);

各ページは独立に再ランキングされるため、2 ページ目の高スコア本が 1 ページ目の低スコア本より下に表示される可能性があります。以前の「出版日ソート」でも同様でしたが、スコア差が大きいとより目立ちます。

7. タイブレークのテストが意図とずれている

  it("should prefer newer books when technical scores are otherwise similar", () => {
    ...
    expect(ranked[0].techScore).toBeGreaterThan(ranked[1].techScore);

改訂版は出版日ボーナス(+12)でスコアが上がるため、「スコア同点時の出版日タイブレーク」ではなく「鮮度ボーナスのテスト」になっています。techScore を揃えた上での comparePublishedDateDesc のテストが欲しいです。

8. parsePublishedDate の重複

tech-book-search.ts は月・日を省略可能、rakuten.tsYYYY年MM月DD日 必須と挙動が微妙に異なります。共通化を検討してください。

9. 型定義の弱さ

app/types/api.tsscoreReasons[].typestring で、ScoreReasonType と乖離しています。

10. テストの抜け

  • pageCount === 0 の減点
  • 著者キーワード
  • debug=truedebug=1 のみ)
  • ムック / 攻略本 の誤判定
  • 真のタイブレーク(同スコア・異なる出版日)

セキュリティ・運用

項目 評価
debug パラメータ 認証必須・スキーマ検証あり。本番でも利用可能だが、スコアリング詳細の露出とレスポンス肥大化の可能性あり。MVP では許容、将来的に本番無効化を検討
techScore の常時返却 問題なし。フロント未使用だが将来の UI 表示に使える
ISBN 検索 再ランキングなし。単件取得なので現状は妥当

総合評価

観点 評価
設計 良い(ドメイン分離・デバッグ設計)
正確性 要修正ムック / 攻略本 の誤減点)
テスト 良好だが、上記エッジケースが不足
パフォーマンス MVP では許容、RegExp 事前コンパイルは改善余地あり
マージ判断 条件付き LGTM — ネガティブキーワード修正後ならマージ可

マージ前にやること(必須)

  1. ムック / 攻略本 のネガティブキーワード見直し
  2. 技術書タイトルでの回帰テスト追加

マージ後のフォローアップ

  • RegExp 事前コンパイル
  • 共通型の整理
  • ページ跨ぎソート(必要なら)
  • タイブレークテストの修正

ネガティブキーワードの修正 PR を出す場合は、その対応も進められます。

Open in Web Open in Cursor 

@Ojoxux Ojoxux changed the title 技術書検索の再ランキングを追加する 技術書検索の関連度による並び替えの実装 Jul 22, 2026
@Ojoxux

Ojoxux commented Jul 22, 2026

Copy link
Copy Markdown
Owner Author

@cursoragent

いくつかなおしたので再レビューたのむ

@cursor

cursor Bot commented Jul 22, 2026

Copy link
Copy Markdown

再レビュー: 技術書検索の関連度による並び替え

前回の指摘の多くが反映されています。4 コミット分(9924bcb6b81c63)を確認し、56 件のユニットテストがパスすることも確認しました。


前回指摘への対応状況

指摘 状態 内容
ムック / 攻略本 の誤減点 ✅ 修正済 NEGATIVE_KEYWORDS から削除。ゲーム攻略 は維持
技術書タイトルの回帰テスト ✅ 追加済 Reactムック基本情報技術者 ポケット攻略本 をカバー
キーワードの二重加点 ✅ 修正済 getNonOverlappingKeywordMatches で最長一致を優先
ページ跨ぎの並び順 ✅ 修正済 mergeRankedSearchResultsBookSearchForm に適用
タイブレークのテスト ✅ 修正済 同スコア時の出版日比較を正しくテスト
テスト不足 ✅ 大幅改善 8 件 → 53 件(ドメイン)+ 3 件(lib)

今回の変更で良くなった点

1. NG キーワードの見直し

const NEGATIVE_KEYWORDS = [
  "漫画",
  ...
  "ゲーム攻略",
  "雑誌",
];

ムック / 攻略本 を外し、ゲーム攻略本 のみ減点対象にするバランスが妥当です。

2. キーワード重複の解消

Web / Web開発AI / 生成AI のように重なるキーワードは最長一致を優先し、二重加点を防いでいます。前回の「無制限加算」懸念にも効いています。

3. 並び替えロジックの共通化

app/lib/book-search-ranking.tscompareRankedSearchResultsmergeRankedSearchResults を切り出し、サーバー・クライアントで同じ比較ロジックを使う構成は良いです。

4. テストの充実

ISBN 正規化、出版社の空白無視、境界値の出版日、入力配列の非破壊、pageCount === 0 の減点など、実運用で効くケースが広くカバーされています。


残っている軽微な点(マージ阻害ではない)

1. parsePublishedDate の重複

book-search-ranking.ts に集約されましたが、rakuten.ts 側にも別実装が残っています(月日必須 vs 省略可で挙動が微妙に異なる)。将来の統一候補です。

2. 型の依存方向

ドメインが依然 BookSearchResultservices/rakuten から import しています。book-search-ranking 共通化の流れで、app/types/ への移動は次のステップとして自然です。

3. RegExp の都度生成

ASCII キーワードごとに new RegExp しています。MVP では問題になりにくいですが、負荷が気になったらモジュールロード時の事前コンパイルを検討。

4. debug=true の結合テスト

debug=1 のみテスト済み。スキーマ上は同等なので実害は小さいです。

5. 重複書籍の扱い

「もっと見る」でページをマージする際、同一 ISBN が複数ページに出た場合の重複排除は未対応。楽天 API の性質上は稀ですが、気になるなら isbn で dedupe できます。


総合評価

観点 前回 今回
正確性 要修正 OK
テスト 不足 十分
UX(ページ跨ぎ) 不整合 修正済
マージ判断 条件付き LGTM

前回ブロッカーだった NG キーワード問題は解消され、テストもしっかり追随しています。設計・実装ともにマージして問題ない水準です。


任意のフォローアップ(マージ後で可)

  • techScorerelevance へのリネーム(命名の好み)
  • parsePublishedDate の完全統一
  • RegExp 事前コンパイル
  • PR 説明文の用語更新(タイトルは「関連度による並び替え」に変わっているが、本文はまだ techScore / 再ランキング表記)
Open in Web Open in Cursor 

…arch-reranking

# Conflicts:
#	app/islands/BookSearchForm.tsx
#	app/server/api/search.test.ts
#	app/types/api.ts
@Ojoxux
Ojoxux marked this pull request as ready for review July 22, 2026 07:53
@Ojoxux Ojoxux self-assigned this Jul 22, 2026
@Ojoxux
Ojoxux merged commit f062947 into staging Jul 22, 2026
9 checks passed
@Ojoxux Ojoxux changed the title 技術書検索の関連度による並び替えの実装 技術書っぽさ検索機能の実装 Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant