⚙️ Vault運用

2026-07-31_作業ログ

最終更新 2026年07月31日 / 90_Meta/Claude作業ログ/2026-07-31_作業ログ.md

作業ログ 2026-07-31

18:33 - /handoff 実行(全テーマ)

このセッションで扱ったテーマ(全部)

  1. SCALE LIST 新規構築 — システム作業 — 本番稼働中(Step6の大半まで完了)
  2. 企業データ基盤の収集 — システム作業 — 公的データ4種を統合済み・連絡先の充足が課題
  3. SalesNowとの比較・戦略判断 — 相談 — 「母数は横並び、差は1社あたりの情報量」で決着
  4. 個人事業主の扱い — 相談 — 一旦対象外と決定
  5. Cloudflareトークン権限の整備 — システム作業 — 完了(以後の依頼不要)

テーマ別の詳細

1. SCALE LIST 新規構築(/system-flow で SalesNow を参考に制作)

現在地: 本番稼働中 https://list.scale-group.co.jp/

  • リポ: ~/dev/scale-list(GitHub scale-group-jp/scale-list・private)
  • Cloudflare Pages scale-list + D1 scale-list-db(id 45456313-b37f-4386-b7e4-9c4b3918d953・APAC)
  • 旧 SCALE List(Next.jsスケルトン14ファイル)は大串判断で破棄し置き換え済み
  • バックアップ ~/scale-list-backups/scale-list-OLD-20260731-1125-before-replace.tar.gz

Step進捗(10ステップ中)
- Step1-3 参考の完全再現(9画面)→ 確認①クリア(大串「バッチリ!」)
- Step4 オリジナル化(オールブラック・SCALE LIST・指紋ゼロ)→ 確認②クリア
- Step5 データモデル設計・CSV取り込み仕様確定
- Step6 バックエンド実装 → 大半完了。API4本稼働・503万件をD1投入済み
- Step7以降 未着手

動いているもの(本番で検証済み)
- 企業一覧が実データ表示(503万件)
- 検索・ソート・ページング
- 絞り込みAPI: 東京都1,173,339 / 東京都+電話あり14,768 / IT 14,671 / IT×受託開発 2,799
- 絞り込みパネルの大業界チェック → 件数が即時更新
- ロゴ(大串支給画像を背景透過でそのまま使用)・ファビコン(マークのみ)

** 未解決の不具合(次の一歩)
-
絞り込みパネルで中業界を開こうとすると一覧画面へ飛んでしまう
- 切り分け済み: 大業界のチェック操作は正常 / API正常 / 業界マスタ正常
- 疑ったが解決しなかった箇所: wireApplyのバー、wireClose(削除済)、app.jsのサイドバー配線(左ペイン限定に修正済)、座標依存の配線(イベント委譲に変更済)
-
どこかに click → /companies へ飛ぶハンドラが残っている**。次はイベントリスナーを列挙して発生源を特定するのが早い

残タスク
- 上記不具合の修正
- 絞り込みパネル残り(所在地・企業規模・ラベル・除外)の接続
- CSVダウンロード / SCALE CRM書き出しボタンの実装(API functions/api/export/crm.js は実装済み・未接続)
- Step7 データ安全ゲート / Step8 著作権最終監査 / Step10 監視・changelog

2. 企業データ基盤の収集

ソース 件数 取得
国税庁 法人番号 5,804,227(存続 5,036,034) 登録不要・月次更新
国税庁 インボイス 2,562,428 登録不要・月次更新
gBizINFO 法人情報 447,611 トークン取得済
gBizINFO 事業所 2,886,775(被保険者数=実質従業員数) 同上
gBizINFO 特許 4,600,382 同上
gBizINFO 補助金 545,877(法人 131,760) 同上
SalesNow CSV 126,564 契約分・月10万件上限

統合マスター companies_master(法人番号が主キー・後から属性を足せる設計)
- 会社名・所在地 100% / 従業員数 55.2% / インボイス 49.4%
- 企業HP 2.7%・電話 1.7% ← 最大の穴

次の一手: Common Crawl の .co.jp 339,384ドメインから公式サイトHTMLを引き、社名・電話・住所を抽出して法人番号に突合。ドメイン一覧は取得済み(_data/cc/)。

3. SalesNowとの比較(相談 → 決着)

  • 当初「2.6倍」と報告したのは誤り。SalesNowのスコア絞り込み後の196万件と比較していた
  • 全件で見ると SalesNow 5,815,288 / 当方の元データ 5,804,227(差0.19%)
  • SalesNowも国税庁の法人番号DBが土台と判明。骨格は横並び
  • 差は1社あたりの情報量(連絡先・スコア)。ここを埋めるのが勝負

4. 個人事業主(相談 → 一旦対象外)

  • インボイス公表サイトに個人分5ファイルあり取得可能。ただし屋号・住所は本人申出時のみ
  • サロン系は自治体オープンデータ(大阪市・京都市・千葉県ほか)が本命だが全国網羅ではない
  • 法的整理: 自社利用は可(利用目的の公表が必要)。架電パートナーへ渡す=第三者提供にはオプトアウト届出が必要
  • → 大串判断で一旦対象外

5. Cloudflareトークン権限

  • 旧トークンはPages専用で、DNS作成もD1作成も不可だった
  • 新トークン取得済み(Pages:Edit / D1:Edit / DNS:Edit)→ ~/.cf_token
  • 旧トークンは ~/.cf_token●●●●●● に退避
  • 以後この種の依頼は不要

このセッション全体の大串FB(重要なやつ全部)

  • 依頼するときはリンクとコピペ値を用意しろ(「Cloudflareダッシュボードで〜してください」だけでは工数がかかる)
  • 業界の判定ロジックは絶対に間違えるな(違う業界が混ざるとヤバい)→ 実際に汚染していたのを指摘され発見
  • 業界区分はSalesNowと完全に同じにする
  • ロゴはフォントで再現せず、支給画像をそのまま背景透過で使う
  • 絞り込みの動きまでSalesNowを完全再現する
  • 名称変遷: SCALE DETA → SCALE DATA → SCALE LIST(確定)
  • 配色: オールブラック(元のエメラルドグリーンを黒へ)
  • JCコードは不要(法人番号を主キーにする方針)

踏んだ技術的な罠(再発防止)

  • Emotion(CSS-in-JS)のCSSはDOM保存に含まれない。CSSOMから別途抽出しないとレイアウトが崩壊
  • Chrome の Private Network Access で参考ページ→localhost の fetch/iframe は全ブロック。トップレベルのフォームPOSTだけが通る
  • D1: 1 INSERT文100行超で SQLITE_TOOBIG / 索引はまとめて作るとCPU時間超過 → 1本ずつ
  • 業界ツリーのDOM抽出: テキストを無差別に拾うと従業員数や前に開いた業界が混入する。チェックボックスのラベルのX座標でペイン判定が正解
  • build/build_theme.py が毎回作り直すので、実装ソースは web/ に置く

21:0x - SCALE LIST 残タスク一括(本番反映済み)

依頼: 「残タスク一気にやろう」→ 引き継ぎに残っていた全項目を実施。

1. 中業界が開けない不具合(最優先)→ 根治

真因: wireApply() が「該当件数の帯」をテキストだけで探しており、
div,span,p,button の中から最短テキストの要素を選んでいた。
参考画面はレイアウトが確定するまで getBoundingClientRect() が全て0を返すため、
「帯でない大きい要素は無視する」というガード(rc.height > 120)が素通り。
結果、パネル全体 div.css-1k8t7d9(1023×568)に「一覧へ飛ぶ」クリックが張り付いていた。

特定の決め手: 本番実機で style="cursor: pointer" が静的HTMLに無いのに
この巨大divへ付与されているのを発見(wireApplybar.style.cursor="pointer" を設定するため)。

対処: web/filters.js を全面書き直し。座標依存の要素特定を全廃し、
id と親子関係だけで特定。帯は <button> に限定。app.js のサイドバー配線からも
座標判定(left < 260)を除去。

2. 絞り込み6画面を全て接続(本番で全て実機確認)

画面 検証結果
業界 IT→中業界11件展開・受託開発で 2,799件
本社所在地 東京都+大阪府で 1,597,640件
企業規模 従業員20-50人 152,052/売上10-50億 24,175/範囲指定100-200人 30,636
企業名 部分一致・全角半角吸収(SCALE=scale=SCALE=57件)
ラベル 企業に付いている印7種・電話番号あり 84,101件
除外 東京都を除く 3,862,695件

レンジの網羅性を機械検証: 従業員10区分・売上8区分・資本金7区分とも、
各区分の合計=全件5,036,034 に完全一致(取りこぼし・重複ゼロ)。

3. 書き出し

  • CSVダウンロード(UTF-8 BOM+CRLF・法人番号キーセット方式・最大5万件)
  • SCALE CRMへ書き出し(件数確認→承諾の2段・INSERT OR IGNORE の新規行のみ)
    crmBound: true で接続確認済み(実際の書き込みは未実行

4. Step7 データ安全ゲート / Step8 著作権監査 / Step10 changelog

  • scripts/check_deployment.sh 新規(画面HTTP・件数下限・業界マスタ・CSV・指紋)→ 全項目クリア
  • 著作権監査で参考システムのGA/GTMタグが残っていたのを発見し除去
    (GTMのnoscript iframe + GA/GTMへのpreconnect)。build_theme.py に恒久組み込み
  • docs/changelog.md 新規

今日いちばん効いた学び

Cloudflare Pages の既定 max-age=14400 で、ブラウザが4時間古いJSを掴み続ける。
修正をデプロイしても直らず、同じ不具合を3回追いかけた。
assets/*.js|css のURLに内容ハッシュ(?v=)を焼き込み、_headers
画面は毎回再検証・assetsは長期キャッシュに変更して根治。
「デプロイしたのに直らない」ときは、まず配信中のファイルとブラウザが読んだURLを確認する。

技術メモ

  • 未知のラベルが来たら「条件なし=全件」ではなく 0件 を返す設計にした。
    絞ったつもりで絞れていないリストを出す方が危ないため
  • 登記上の商号は英数字が全角(株式会社SCALE)。半角では引けない
  • 部分一致 LIKE '%…%' は503万件フルスキャンで約500ms。OR を並べても走査は1回
  • Cloudflare は Python の既定 User-Agent を 403 で弾く。日本語パラメータは要URLエンコード

21:5x - SCALE LIST 書体と絞り込みの見た目を参考へ完全統一

大串FB: 「フォントとか全部SalesNowと一緒にしてる?なんか若干違うかも。全く一緒にして欲しい。絞り込みしたときに出るフォントとかそういうのも統一して」

真因(書体が違っていた)

参考のCSSは Noto Sans JP Variable を指定し、実体を /_next/static/media/*.woff2
(参考システム側のパス)から読んでいた。そのファイルは成果物に無いので @font-face 124本が
全て空振りし、OS既定のゴシックで表示されていた

HTMLが読んでいた Google Fonts は Noto Sans JP で、CSSの指定名と一致せず使われていなかった。

→ 実体の無い @font-face を削除し、ファミリー名を Noto Sans JP に統一。
Google Fonts も参考と同じ可変ウェイト(wght@100..900)・display=swap へ。
bundle.css は @font-face だけのファイルだったので空になり、link ごと除去。

絞り込みの生成部分(自前で描いていた箇所)

参考の行は 14px / 400 / line-height 14px / #4E4E4E / チェック枠は16pxのSVG。
私は 13px / #333 の素のチェックボックスで描いていた=これが「若干違う」の中身。

参考のチェックボックス1行を <template> でビルド時に全画面へ仕込み、実物を複製する方式に変更。
本番実機で参考の行と生成した行の計算後スタイルが全項目一致することを確認。

→ チェックしても枠が変わらない問題も判明(参考から保存できたのは未チェックの空枠SVGだけで、
チェック済みの絵は元システムがJSで差し替えていた)。黒地+白いチェックを重ねて表現。

今日3回踏んだ同じ形の罠

「要素をテキストや座標で探す」と必ず壊れる。
1. 帯を最短テキストで探す → パネル全体にクリックが付き一覧へ飛ぶ
2. 座標でペイン判定 → レイアウト確定前は全部0で判定総崩れ
3. 帯を文字で探す → 件数計算中に文字が変わり見失い、選択肢が画面外へ

ビルド時に id="sl-apply" を振り、JSは id で掴む形に統一して根治。
リポのCLAUDE.mdに明記した。


23:2x - SCALE LIST 連絡先データを収集(電話が1.33倍)+ 監視(Step10)

依頼: 「残タスク進めて」

成果(本番反映済み)

電話番号あり 84,101 112,106(+28,005)
問い合わせフォームあり ほぼ無し 62,884

65,343社の公式サイトを見て 36,773件で電話を検出(56.3%)。
検証を通った 28,005件(76.2%)を採用。

架電できる会社(本番実測): 東京都 20,234 / IT 7,981 / 従業員20〜50人 43,444

検証で落とした8,768件(そのまま入れていたら架電事故)

件数 理由
所在地と食い違い 5,788 (15.7%) 他社の番号を拾っていた
存在しない局番 1,950 (5.3%) 000-000-0000 等・番号ですらない数字列
携帯番号 1,030 (2.8%) 個人の番号のおそれ

6件に1件が違う会社の番号だった。 市外局番から地域を割り出し、
本社の都道府県と突き合わせて機械的に落とす仕組みを入れた(verify_contacts.py)。

サンプルを増やすほど食い違い率が上がった(200件で10.2% → 2万件で16.7% → 全件15.7%)。
小さいサンプルで「大丈夫そう」と判断していたら危なかった。

設計上の要点

  • 対象を「HPが分かっている会社」に限った=どの会社の番号か取り違える余地が無い
  • robots.txt 遵守・1社最大2ページ・UAに連絡先明記・200件ごとに保存で再開可能
  • 携帯番号は採らない(法人の公表連絡先に限る方針)

技術的な罠(実測)

D1へのUPDATEで CASE corporate_number WHEN ... END に400件まとめたら
1文2.4秒・1ファイル122秒で接続断。
(400件 × 400分岐の総当たり比較になる)
主キーを1件ずつ指定するUPDATEに変えたら2,000件0.35秒。350倍速い。

本番D1は列を絞ってあり phone_src 等の _src 列を持たない(手元DBのみ)。

Step10 監視

scripts/monitor.sh + launchd com.scale.scale-list-monitor(毎朝8時)。
check_deployment.sh を流し落ちたときだけ Slack へ流す(毎朝通知は読まなくなるため)。
ログ=~/Library/Logs/scale-list/monitor.log
通知先は環境変数 SCALE_LIST_ALERT_CHANNEL で有効化。

次の一手

  • 電話が取れなかった 28,570社(HPはあるが番号が載っていない・取得失敗)
  • 未活用の co.jp ドメイン 32万件match_domains.py は厳しく絞って15,672件しか当てていない)
    → サイトを実際に読んで社名・住所で突合すれば、HPも電話も同時に増やせる