取材で受けた機能要望 4 件の実装(2026-10-08〜09)

interview-analysis-request-2026-10-05 の取材対応の中で、相手から 4 件の機能要望が出た。2026-10-08 夜から 10-09 未明にかけて、work/kouchou-ai の 3 つの worktree で並行して実装した。取材の内容・提供データ・そのデータでの数値は非公開のまま(raw に保管)で、このページには設計判断と検証状況だけを残す。

要望branch(main a12d68e 起点、未push)状態
ヘイト表現の比率を出す / 政策提案に絞った地図feat/opinion-speech-classification実装・テスト済み。非公開データで全件試行済み
全画面表示のまま大きい群と細かい群を切り替えるfeat/fullscreen-cluster-level実装・テスト済み。実ブラウザ未確認
全画面表示のまま日本語・英語・韓国語を切り替えるfeat/fullscreen-language-switch実装・テスト済み。翻訳は一部デモのみ

1. 意見分類: 比率と絞り込みは同じ仕組みで足りる

「ヘイトの比率」と「政策提案だけの地図」は別機能に見えるが、どちらも意見 1 件ごとにラベルを付け、属性として結果に載せることで同時に満たせる。属性にしておけば公開ビューアの既存の属性フィルタがそのまま効き、ビューア側のコード変更が要らない。

  • ラベルは独立した yes/no を 2 つ: hate_or_insult(侮辱・罵倒・蔑称、属性に基づく差別・排除・敵意、危害を望む表現)と policy_proposal(公共の政策・制度への具体的な対応・方向性、特定政策への明示的な賛否)。「生産的な政策議論」は「政策提案かつ非侮辱」と定義した。
  • 強い批判・辞任や処罰の要求・皮肉は侮辱に含めない。人事だけを求めるもの・番組への要望は政策提案に含めない。定義はプロンプトに明記し、steps/opinion_labels.py を正本にした。
  • hate_or_insult は法的・学術的な「ヘイトスピーチ」の定義ではなく作業定義である。比率はこの定義に相対的な数字で、分母は抽出された意見数(投稿数でも人数でもない)。
  • workflow の任意ステップとして追加し(plugin-system の builtin plugin、cli から有効化)、hierarchical_aggregation で属性に載せる。fake LLM でのテストを含め pytest 282 件、ruff clean。
  • 非公開データでローカル LLM(Qwen3.8 27B)を使って全件試行した。サンプル 100 件の目視では誤検出より見落としが多く、数字は下限寄りに読むべきと分かった。local-llm-extraction-faithfulness-2026-10-05 と同じく、韓国語から日本語への抽出段階で表現が和らいでいる可能性もある。
  • 既存のクラスタ名(「侮辱的言説の集積」のような見出し)とこのラベルは別の仕組みで、件数は一致しない。試行では侮辱判定された意見が level 1 の全群に散っていた。見出しだけで比率を語れない、という interview-analysis-request-2026-10-05 以来の注意が裏付けられた。

2. 全画面の粒度切替: 新しい状態を増やさない

全画面中は「全体 / 詳細クラスタ」の選択が画面外にあり、切り替えるには全画面を抜ける必要があった。

  • 新しい state は増やさず、ClientContainer が持つ表示選択(selectedChart)を全画面内から切り替える。「全体」= level 1、「詳細クラスタ」= 最深 level、「濃い意見」= 最深 level + 密度フィルタ、という既存の対応をそのまま使うので、全画面を抜けても選択が引き継がれ、密度設定とも整合する。Plotly の uirevision でズームは保持される。
  • FullscreenToolbar(右端に全画面終了を固定し、左側を children スロットにした容器)と ScatterLevelSwitch(aria-pressed 付きのボタン群)を追加。通常のセレクタと同じ無効判定を resolveChartModes() に切り出して共有した。
  • issue #283 は「モードバーが全画面終了ボタンに被る」バグ(クローズ済み)で、ツールバーの設計提案ではなかった。その退避処理が参照する #fullScreenButtons の id は維持した。#306、#933 にも粒度切替の既存設計は無い。
  • Jest 132 件、tsc、通常 build と shell build が通る。静的 shell 配布物でも動く純クライアント処理。

3. 日英韓切替: 訳は結果 JSON に載せ、ビューアは差し替えるだけ

open issue #323「レポートの多言語対応」に沿う設計にした。UI の文言と、レポート本文(群名・説明・概要)の 2 層を分けて扱う。

  • UI 文言は i18n フレームワークを入れず、lib/i18n/messages.ts に ja を正本として en/ko を型で欠落検出する最小の辞書にした。Plotly の韓国語ロケールを追加。
  • レポート本文の訳は hierarchical_result.json の既存フィールド translations(TTTC 由来で今まで常に空だった)に、言語ごとに群 id → {label, takeaway}、overview、question、intro、任意で意見本文、を入れる。全フィールド省略可で、訳が無い部分は元の言語のまま表示し「元の言語で表示しています」と注記する。
  • 選択は URL の ?lang= と localStorage の両方に保存し、history.replaceState だけで Next のルーターに触れないので静的 shell でも動く。
  • analysis-core に後処理 CLI kouchou-translate を追加(--languages en,ko、--include-arguments、--dry-run)。意見本文の翻訳は既定でしない(群名の数倍の費用)。
  • 全画面では既存の #fullScreenButtons に言語トグルを 1 行足しただけなので、2 の FullscreenToolbar に並べられる。統合時の競合候補はこの 1 ブロックだけ。
  • Jest 153 件、build、shell build、pytest 270 件、ruff clean。ローカル LLM でのデモ翻訳は速度が遅く(約 2 tok/s)、上位の群と概要だけで止めた。韓国語は自然だが文体が混在し、英語の群名は正確だが長い。

検証で分かった環境の制約

  • 実ブラウザでの表示確認は 3 ブランチとも未実施。WSL 内の dev サーバーに Windows 側のブラウザから到達できず、WSL に Playwright ブラウザも無い。特に幅 390px でツールバーが折り返さないかは目視が要る。
  • Ollama は Windows 側の loopback にしか listen しないため、WSL から直接は呼べない。分類と翻訳の試行は、analysis-core の定義ファイルを直接読み込む stdlib-only のランナーを Windows 側の Python で走らせた。本体のステップは OpenAI 互換 API 経由で、同じプロンプトとパーサを使う。
  • main に Biome のエラーが 6〜7 件元から残っている(faq/Contact.tsx、shell-data.test.ts の noDelete、format 数件)。今回の変更ファイルはすべて clean だが、全体 lint は通らない。

Open Questions

  • hate_or_insult の作業定義を外部にどう説明するか。見落とし寄りの数字を「下限」として出してよいか。
  • 粒度切替に「濃い意見」を残すか。ボタン文言を要望どおり「大きい / 細かい」に寄せるか。言語トグルがあるなら aria-label="クラスタの粒度" も翻訳対象。
  • 意見本文まで翻訳するか。外部に見せるなら OpenAI で全件翻訳し、固有名詞の訳語を人手確認する必要がある。
  • 翻訳ステップを workflow に組み込むか(今は後処理 CLI のみ。API / admin からの導線も無い)。
  • 既存の Biome エラーを別途直すか。 2026-10-09: #962 / PR #963 で 0 件化と CI での強制を進めることになった(biome-lint-main-drift-2026-10-09)。
  • 3 branch の統合順序と、push 認証の設定後に PR を課題ごとに分けるか。

Updates

  • 2026-10-09: 初版。3 branch とも未push、ローカルコミットのみ。

  • 2026-10-09 午後: 粒度切替を実ブラウザ(Chromium、1280 / 390 / 320px)で観察。PC 幅では要望どおり動き、390px 以下では 3 ボタンが横スクロールになる。西尾の判断は「全画面で切り替えるのは PC でやること」で、スマホ幅は直さない。状態カタログの E2E に 1 件追加して 6 件成功。Issue #960 を起票し、PR #961(feat/fullscreen-cluster-level、3 commit)を提出した。CLA チェックとスクリーンショット貼付は西尾が行う。残り 2 branch は未push。

  • 2026-10-09 夕方: PR #961 は CodeRabbit の指摘(密度フィルタで最深 level が全て落ちると全画面側だけ「詳細クラスタ」が無効になる)を modeResult の追加で直し、CI 全通過・CodeRabbit 了承。比較用スクリーンショットは状態カタログの 12 件では差が見えず、dummy-server の example レポート(約 9,500 件)で 16 倍に拡大して撮り直した。残る分類 branch と言語切替 branch は会社側の Claude Code で続ける方針(西尾)。git bundle と引き継ぎ書を渡した。言語切替 branch は #961 と Chart.tsx などで衝突するので、#961 マージ後に rebase する。渡し方は「GALLERIA で用意 → Mac が吸い取って会社側へ送る」で、公開 repo への branch push は使わない(西尾の方針)。同日、会社側へ送信済み。PR 化は会社側で文面まで作り、出すかは西尾が中身を見てから決める。

  • 2026-10-09 夜: 非公開の確認用資料を一つの入口に集約し、比率画像、絞り込み条件が分かる地図、同じ群IDで日英韓を横に並べる照合表を作成。分類の作業定義、分母、翻訳対象と人手未検証の範囲を表示する。提供データの具体的な数値・内容は公開範囲外。

  • 同日: 確認用branch codex/korea-review-preview(ローカル、未PR)で、全画面の粒度切替の辞書適用漏れと英語ラベルの単語途中の改行を補正。関連Jest 6件とTypeScript検査・shell build成功、実画面でja/en/ko切替を確認。元の座標を維持した絞り込みでは、元の群名・説明が絞り込み後だけの要約にはならないことも明示した。

  • 公開境界の補正: 実データの具体的な意見件数をこのページから除いた。成果物・検証の詳細は非公開領域で管理する。

  • 2026-10-10: 個別の抽出意見にも既存の translations.arguments を適用し、レポートの言語切替・同一IDでの照合表・原文ページに訳を接続した。抽出文の翻訳と投稿原文を別に表示し、原文自体は保持する。結果は非公開の確認資料で、翻訳は人手未校正。

  • 2026-10-10: 確認担当者向けの作業用入口と、資料を初めて読む相手向けの目次を分けた。目次は日英韓に対応し、選択した分類の意見・投稿原文・地図へ案内する。単純な比率は棒グラフを外して文章で示す。資料は非公開・未送付のまま。

  • 2026-10-10: 読者向け目次を地図のスクリーンショット中心に変更し、意見全文・原文への導線を任意の補助リンクにした。既存レポートの一覧も置き、各レポートの日英韓版へ直接進めるようにした。

  • 2026-10-10: ユーザーが提示した取材前後のメモと参照ページを読解し、既存の文字起こしに時刻・参照先を対応づけた。メモ本文の最終読解日は同日。開いていた資料と実際の発言を同一視せず、表記候補・資料側の事実・録音確認待ちを分けて非公開の補助記録に保存した。音声の再聴取・全文校正は未完了。

  • 2026-10-10: ユーザー報告で確認資料の案内メール送付を把握。並行して届いた追加依頼を、既存成果・追加集計・検証手順・掲載条件に切り分け、非公開記録へ保存した。除外対象だけの地図と除外後の地図は別の成果物であり、チャンネル属性の混在を個人の立場や合意と混同しない。相手方の記事掲載条件と、こちらの事例公開への許諾も別に扱う。

  • 2026-10-10 夜: 追加依頼に応じ、非公開資料に出典チャンネル別の色分け、同一座標での除外前後比較、群別の構成・除外率、既存選択の暫定主題整理を追加した。複数出典の意見は代表投稿だけで帰属を決めず、元の対応表を使い、表示と集計の重複処理を明示する。選択済み集合の主題整理と、選択基準自体の再検証を区別し、曖昧な項目を無断で除外しない。画像・表・照合CSVを日英韓の入口から確認でき、除外後の全文表示も動作確認済み。成果物は非公開snapshotと新しいbundleに保存し、以前の送付版は保持した。送付・外部公開は未実施。