Skip to content

♻️ Clubs との突合を dojo2dojo.csv から global_club_id に切り替える - #42

Merged
yasulab merged 6 commits into
mainfrom
use-global-club-id-for-matching
Aug 29, 2026
Merged

yasulab merged 6 commits into
mainfrom
use-global-club-id-for-matching

Conversation

@yasulab

@yasulab yasulab commented Aug 6, 2026

Copy link
Copy Markdown
Member

coderdojo.jp の active な全エントリが global_club_id を持つようになった(coderdojo.jp#1900)ので、Clubs DB との突合を名前照合から UUID 直結に切り替え、dojo2dojo.csv(301 行)を削除します。

名前照合の何が問題だったか

問題 実例
新しい Dojo のたびに dojo2dojo.csv へ 1 行足す運用が必要 追加漏れが実際に起きていた
Clubs 側で改名されると追従できない 那覇の登録名が変わっていた
同名クラブが二重登録されていると先に現れた方を拾う 流山・古河

同じ入力データで新旧の出力を比較しました

「地図が変わらないこと」を確かめるため、同じ _data/*.json に対して main のスクリプトと本 PR のスクリプトを走らせ、出力を突き合わせています。

main(名前照合) 本 PR(UUID 直結)
日本のマーカー 195 202
GeoJSON features 1,309 1,316

地図から消えた Dojo: 0 件

増えた 7 件(CSV に行が無く漏れていたもの)

Dojo Clubs 上の登録名
播磨科学公園都市 テクノ@光都
八戸@吹上 八戸@鮫
川西 CoderDojo川西
明石 Akashi @ Akashi City Office
吉祥寺 Coderdojo Kichijoji
氷見@bigberries Himi @ Big Berries
豊橋 CoderDojo Toyohashi

名前が一致しないもの(八戸@吹上八戸@鮫)や表記ゆれ(CoderdojoCoderDojo)が並んでいます。名前照合が構造的に取りこぼしていた分です。

これで active な 202 エントリが全件地図に載ります(突合できなかったもの 0 件)。

座標が動いた 2 件(流山・古河)— いずれも約 0.1 km

流山: [139.937825, 35.877079] -> [139.938719, 35.876549]
古河: [139.711416, 36.175635] -> [139.710699, 36.176853]

Clubs 側に同名クラブが 2 件ずつ登録されており、main は「名前で先に見つかった方」を拾っていました。本 PR は coderdojo.jp が global_club_id で指定した方を選びます。

Nagareyama  46a7f225-...  ← main が拾っていた
Nagareyama  7d3b2a2d-...  ← coderdojo.jp が指定している方

同じ町内での移動で、どちらを拾うかが Clubs API の返却順まかせでなくなった結果です。

名前で救済する経路は置きません

active な全エントリが global_club_id を持つことは coderdojo.jp 側の spec が保証しており、UUID の無い Dojo が入るとあちらの CI が落ちます。こちらに手動のマッピングを残す理由がなくなりました。

あわせて、名前照合を前提にしていた次のものを削除しています。

静かに壊れる経路を塞ぎます

名前照合には「壊れても縮退して生き延びる」性質がありました。_data/dojos_japan.json が空でも、CSV とローカルのデータだけで日本のマーカーは出ていました(ロゴがデフォルトになるだけ)。UUID 直結にはその性質が無く、入力が空なら日本のマーカーは全滅します。

海外分 1,100 件あまりは残るため、既存の GeoJSON 検査では検出できません。次の 4 つで塞ぎます。

追加 内容
scheduler_daily.yml の件数ガード 日本側データが 300 件未満なら停止(Earth 側の 1000 件ガードと同じ形)
deploy_to_pages.yml の件数・UUID 数ガード 取得は成功したが中身が劣化した場合に、コミット済みデータへ復元させる
tests/dojo_matching_test.rb 入力と出力の両側に下限。UUID の書式・一意性、マーカーの重複を検査
突合レポート + Slack 通知 載らなかった active な Dojo を理由付きで書き出し、要対応のものだけ通知

deploy_to_pages.yml のガードを足した理由

Earth 側には件数チェックがあるのに Japan 側には無く、挙動が割れていました。

状況 ガード追加前
取得が例外で死ぬ 復元パス → コミット済みデータでデプロイ成功
取得は成功するが中身が劣化 復元パスを素通り → 後段の rake testデプロイ全体がブロック

後者はこのステップが明記する目的(「Geolonia 障害時の緊急デプロイが外部 API の状態に左右されてはならない」)を破ります。劣化時は復元パスへ落とすようにしました。

入力 DOJOS UUIDS 判定
正常 340 264 通過
global_club_id 消失 340 0 発火
空配列 0 0 発火
一部だけ取得 100 68 発火

通知対象に no_uuid を加えました

当初は uuid_not_in_clubs だけを通知し、「UUID が無い状態は新規追加直後にも起きるので通知しない」としていました。UUID 必須化でこの前提が逆転しています。 coderdojo.jp の spec が全 active に UUID を強制する今、no_uuid が出たら上流の保証が破れた信号なので、通知します。

club_excluded_by_status_or_coordinates(Clubs 側で座標が消えた・活動中でなくなった)はこちらから直せないため、ログのみに留めます。

検証

  • bundle exec rake test → minitest 14 runs + 7 runs / 0 failures、HTML-Proofer 成功
  • RED を確認: next if japan_dojo.nil? を外すと、日本の Dojo が海外用フォールバックで描画されテストが落ちることを実測
  • 劣化データ 3 パターンで deploy_to_pages.yml のガードが発火することを実測(上表)
  • 通知判定を実データで実行し、正常時は通知しないことを確認

ついでに整理したもの

対象 内容
dojo2dojo.csv 前提の記述 CLAUDE.md のトラブルシューティング手順を UUID 突合のものに差し替え。deploy_to_pages.yml のコメントも更新
fetch_latest_data.sh 存在しない rake cache_dojo_logos を呼んでいた(#44 でロゴキャッシュを消した際の取り残し)
/dojo2dojo.json/dojos.json dojo2dojo は CSV に由来する名前で、CSV を消すと指すものが無くなる。中身は「地図に載った日本の Dojo 一覧」なので dojos.geojson の隣に dojos.json が並ぶ形にした。日本の 202 件のみで海外は含まないため、その旨をページに明記している。⚠️ 旧 URL は残していません
_plugins/build_hooks.rb system() の終了コードを捨てていたため、GeoJSON の生成が失敗してもビルドは成功し、コミット済みの古い地図がそのまま出荷されていたexception: true を足して止める

最後の 1 件は、わざと失敗させてビルドが停止することを確認しています。

Command failed with status (1): [ruby _tasks/upsert_dojos_geojson.rb]
_plugins/build_hooks.rb:13:in 'Kernel#system': Command failed with exit 1

PR でテストが走っていませんでした

作業中に気づいた別の問題です。ワークフロー 5 つのうち PR で起動するのは claude-reviewdependabot だけで、どちらもテストを走らせていませんでした

bundle exec rake testdeploy_to_pages.yml の中にあり、main への push 時にしか走りません。つまりテストが落ちるのはマージした後で、デプロイがブロックされる形です。

本 PR は安全性の根拠を新しいテストに置いているのに、その根拠が PR 上で一度も検証されない構造でした。on:pull_request を足し、次の 2 ステップを PR ではスキップします。

ステップ PR での扱い 理由
🌐 データ取得 スキップ コミット済みデータだけで検証すれば、PR の結果が外部 API の状態で揺れない
🔧 Build & Test 実行 これが目的
🔑 API キー注入 スキップ デプロイしないので不要。fork からの PR には secrets が渡らない
🚀 Deploy 実行されない 元から github.ref == 'refs/heads/main' で守られている

pull_request イベントの github.ref は常に refs/pull/N/merge になるため、fork 側のブランチ名が main でもデプロイ条件には一致しません。また fork からの PR では GITHUB_TOKEN が read-only で secrets も渡らないため、仮に条件をすり抜けても push は失敗します。

データ取得をスキップした時 steps.fetch.outcome'skipped' になるので、復元ステップ(== 'failure')も正しくスキップされます。

ci.yml に改名しました

テストとデプロイの両方を持つ実態に名前を合わせ、ジョブにも表示名を付けました(PR のチェック欄に deploy とだけ出るのは実態と違うため)。

branch protection とルールセットがいずれも未設定であることを確認済みで、required check 名の互換問題は起きません。将来設定した後だと改名が破壊的変更になるため、今のうちに直しています。README のバッジと CLAUDE.md の手順も追従させました。

なお、これで防げるのは「リポジトリの中身が原因でテストが落ちる」場合です。main への push 時は取得した実データでテストするため、「PR は green だがマージ後に外部データ起因で赤」は残ります(その場合は復元パスで縮退します)。

この PR に含めていないもの

連名道場の個別表示は含めません。西宮・梅田(2 箇所)と大田・邑南、他(7 箇所)は 1 エントリ 1 マーカーのままで、7 箇所が地図に出ない状態が続きます。これは本 PR で生じるものではなく、counter の再設計と掲載名変更の確認を伴う別の話です。

db/dojos.yml の該当エントリがどのクラブを指すかは確定済みなので、分割の前提は整っています。

マージ後にやること

coderdojo.jpdoc/how_to_add_dojo.md に、本 PR で不要・不正確になる記述があります。マージ後すぐに doc の PR を出す必要があります。

箇所 対応
「DojoMap への反映(暫定手順)」節(160〜190 行) 丸ごと削除dojo2dojo.csv に 1 行足す手順そのもの。doc 自身にも「UUID で突合するようになったら、この節は丸ごと削除してください」と書いてある
手順 9「マージ後、DojoMap への反映の 1 行を追加する」 削除。この作業自体が不要になる
反映確認の URL https://map.coderdojo.jp/dojo2dojo.json https://map.coderdojo.jp/dojos.json に差し替え
global_club_id の「申請に「承認確認」URL が無い場合は省略してください」 要検討。active な Dojo で省略すると spec が落ちる

クラブ名を手で対応付ける dojo2dojo.csv (301 行) をやめ、coderdojo.jp の
global_club_id で直接突き合わせる。CSV は削除した。

名前での突合には次の問題があった。

- 新しい Dojo を追加するたび CSV に 1 行足す運用が必要で、実際に漏れていた
- Clubs 側で改名されると追従できない
- 同名クラブが二重登録されていると、先に現れた方を拾ってしまう

active な全エントリが global_club_id を持つことは coderdojo.jp 側の spec が
保証しているため、名前で救済する経路は置いていない。

## 同じ入力データで新旧の出力を比較した結果

- 日本のマーカー 195 -> 202 件。地図から消えた Dojo は 0 件
- 増えた 7 件は CSV に行が無く漏れていたもの
  播磨科学公園都市 / 八戸@吹上 / 川西 / 明石 / 吉祥寺 / 氷見@bigberries / 豊橋
- 流山・古河は約 0.1km 移動。Clubs 側の同名二重登録のうち、coderdojo.jp が
  指定した UUID のクラブを選ぶようになったため

## あわせて整理したもの

- tmp/unmatched_dojos.json と Slack 通知を追加。CSV が無くなった今、Clubs 側の
  削除・ID 変更を検知できるのはこれだけになる
- deploy_to_pages.yml のデータ取得に Japan 側の件数・UUID 数チェックを追加。
  取得は成功したが中身が劣化した場合に、コミット済みデータへ復元させる
- 突合の情報源が Japan DB 自体になり到達不能となったガードを削除 (#43)
- 名前突合を前提にしたテスト・ドキュメントを更新
@yasulab
yasulab force-pushed the use-global-club-id-for-matching branch from a0fc229 to 5c2eade Compare August 29, 2026 15:42
@yasulab yasulab changed the title feat: Clubs との突合を global_club_id (UUID) に切り替える ♻️ Clubs との突合を dojo2dojo.csv から global_club_id に切り替える Aug 29, 2026
## 一時エンドポイント /dojo2dojo.json を削除

`_data/dojo2dojo.json` をそのまま公開するページで、名寄せデータの比較用として
2025年12月に一時的に追加したもの (54b8547)。UUID 突合に切り替わり、名前の
対応付けを目視で比べる必要が無くなったため削除する。

このページを参照しているコード・リンクは無いことを確認済み。生成物である
`_data/dojo2dojo.json` 自体は残る (テストが下限を検査している)。

## 生成タスクの終了コードを見る

`_plugins/build_hooks.rb` が `system()` の戻り値を捨てていたため、GeoJSON の
生成が失敗しても Jekyll のビルドは成功し、コミット済みの古い地図がそのまま
出荷されていた。突合を global_club_id に切り替えてこの生成への依存が上がった
ので、静かに古いものを出すより止める方が安全になる。

わざと失敗させて、ビルドが停止することを確認した。

    Command failed with status (1): [ruby _tasks/upsert_dojos_geojson.rb]
    _plugins/build_hooks.rb:13:in 'Kernel#system': Command failed with exit 1
## PR でテストが走っていなかった

このリポジトリのワークフロー 5 つのうち、PR で起動するのは claude-review と
dependabot だけで、どちらもテストを走らせていなかった。`bundle exec rake test`
は deploy_to_pages.yml の中にあり、main への push 時にしか走らない。つまり
テストが落ちるのはマージした後で、デプロイがブロックされる形になっていた。

突合を global_club_id に切り替えた PR #42 は、安全性の根拠を新しいテスト
(tests/dojo_matching_test.rb の件数下限・UUID 検査) に置いている。その根拠が
PR 上で一度も検証されない状態だった。

## PR ではデプロイ手前まで走らせる

`on:` に pull_request を足し、2 つのステップを PR ではスキップする。

- データ取得: コミット済みのデータだけでビルドすることで、PR の結果が外部 API の
  状態で揺れなくなり、リポジトリの中身そのものを検証できる
- API キー注入: デプロイしないので不要。fork からの PR には secrets が渡らない

デプロイのステップは元から `github.ref == 'refs/heads/main'` で守られている。
pull_request イベントの github.ref は常に refs/pull/N/merge になるため、fork 側の
ブランチ名が main でも一致しない。

fetch をスキップした時 steps.fetch.outcome は 'skipped' になるので、復元ステップ
(`== 'failure'`) も正しくスキップされる。

## ci.yml に改名

テストとデプロイの両方を持つ実態に名前を合わせる。あわせてジョブに表示名を付けた。
PR のチェック欄に「deploy」とだけ出るのは実態と違うため。

branch protection と ruleset がいずれも未設定であることを確認しており、
required check 名の互換問題は起きない。将来設定した後だと改名が破壊的変更に
なるので、今のうちに直しておく。

README のバッジと CLAUDE.md の手動実行手順も追従させた。
前のコミットで「役目を終えた一時エンドポイント」として削除したが、これは
誤りだった。coderdojo.jp の doc/how_to_add_dojo.md が、新しい Dojo が地図に
出たかを確認する手順でこの URL を使っている。

    curl -s https://map.coderdojo.jp/dojo2dojo.json | ruby -rjson -e '...'

削除前に参照を調べたが、map リポジトリ内しか見ておらず、coderdojo.jp 側を
見ていなかった。追加された経緯(名寄せデータの比較用)は確かに終わっている
ものの、その後この確認手順という別の用途が付いていた。

配信内容は突合の結果そのもので、global_club_id と突合先のクラブ名が分かる。
UUID 突合に切り替えた後もこの用途には合っているため、名前は変えずに残す。
何のためのファイルかを front matter のコメントに書いた(Jekyll のコメントは
出力に現れないので、配信される JSON は変わらない)。
dojo2dojo という名前は dojo2dojo.csv(Japan 登録名 ↔ Clubs 登録名の対応表)に
由来していた。CSV を消したので、この名前が指すものは存在しなくなった。

配信している中身は「地図に載った日本の Dojo 一覧」で、どの Clubs 上のクラブと
突合したかが分かる。dojos.geojson の隣に dojos.json が並ぶ形になる。

    https://map.coderdojo.jp/dojos.json

日本の Dojo 202 件だけで、地図に出ている海外のクラブは含まない。名前からは
読み取れないため、ページの front matter に明記した。

生成物のファイル名 (_data/) も揃えた。

このエンドポイントは coderdojo.jp の doc/how_to_add_dojo.md が掲載後の反映確認で
参照している。旧 URL は残さないので、同 doc の URL を差し替える必要がある。
同 doc の「DojoMap への反映(暫定手順)」節は、CSV に 1 行足す手順そのもので
本 PR のマージで不要になるため、どのみち書き直しが要る。
Clubs 側の登録名をそのまま引用していたが、伝えたいのは「改名されると
追従できない」ことなので、変わった事実だけ書けば足りる。
@yasulab
yasulab merged commit ba221ac into main Aug 29, 2026
3 checks passed
@yasulab
yasulab deleted the use-global-club-id-for-matching branch August 29, 2026 16:18
yasulab added a commit to coderdojo-japan/coderdojo.jp that referenced this pull request Aug 29, 2026
DojoMap が global_club_id (UUID) で突合するようになり、地図側で
dojo2dojo.csv に 1 行足す作業が不要になった。手順書がその作業を
指示したままだったので、現状に合わせる。

- 手順一覧から「マージ後、DojoMap への反映の 1 行を追加する」を削除
- 「DojoMap への反映(暫定手順)」節を、UUID 突合の説明に置き換え
  - 反映のタイミング(翌朝 5:59 JST の日次 Actions)と即時実行の方法
  - 確認 URL を /dojo2dojo.json から /dojos.json に差し替え
  - 地図に出ない時の切り分け。Slack に通知が飛ぶのは
    global_club_id が Clubs 上のクラブと一致しない場合だけで、
    座標・ステータスの問題は Actions のログにしか出ない

あわせて、この変更で読み手が誤解しうる2箇所を直した。

- global_club_id は「省略可」と書いてあったが、休止・閉鎖していない
  Dojo では必須になっている(spec が落ちる)。承認確認 URL が無い
  場合の探し方も、実行できるコマンドと注意点を添えた。Clubs 上の
  登録名は掲載名と大きく異なることがあり、掲載名では引けない
- フィールド表の is_active は実装と食い違っていた。db/dojos.yml に
  is_active は 0 件で、lib/tasks/dojos.rake が読むのは
  inactivated_at のみ。YAML に is_active: false と書いても効かない

cf. coderdojo-japan/map.coderdojo.jp#42
yasulab added a commit that referenced this pull request Aug 31, 2026
見直しで、実装に無いものを案内している記述が見つかった。

## 存在しない cache_dojo_logos の案内 (6 箇所)

ロゴの取り込みは #44 でやめ、coderdojo.jp から直接読み込む方式にした。
その際 CLAUDE.md と README を直しておらず、実行するとエラーになる
コマンドを案内したままだった。

    bundle exec rake cache_dojo_logos    # このタスクは存在しない

`_tasks/cache_dojo_logos.rb` も Rakefile のタスクも無い。ビルドフックが
実行するのは upsert_dojos_geojson と compact_geojson の 2 つだけ。
`images/dojos/*.webp` も同じ理由で残骸になっていた。

## 実在しないスクリプト名 (3 箇所)

    get_dojos_from_earth.rb  -> get_data_from_earth.rb
    get_dojos_from_japan.rb  -> get_data_from_japan.rb
    get_events_from_japan.rb -> 存在しない (イベントは get_data_from_japan.rb が取得する)

## Slack 通知の対象が、スクリプトとワークフローで食い違っていた

upsert_dojos_geojson.rb のコメントは「uuid_not_in_clubs だけが要対応」の
ままだったが、#42 で no_uuid も通知対象にしている。no_uuid は coderdojo.jp
側の spec が防いでいるはずの状態で、出たら上流の保証が破れている。
club_excluded_by_status_or_coordinates を通知しない理由も併記した。

## ステータスの説明が不正確だった

「座標が無いか活動中ではない」と書いていたが、PLANNING (準備中) も地図に
出すため「準備中・活動中のどちらでもない」が正しい。coderdojo.jp 側の
手順書は直していたが、こちらを直し忘れていた。
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