Repository navigation
feat: law_summary.py に条文を根拠として渡す(#16)+ 4法令の概要を埋める(#14) - #19
Merged
Merged
Conversation
law_summary.py が extract_text / walk_tags を必要としたため、diff.py から 純粋な移動として切り出す。llm.py が生まれたのと同じ理由 — 3本の生成系が それぞれヘルパのコピーを持ったことが、そもそも互いに drift した原因だった。 振る舞いは変えていない(既存 87 件が緑のまま)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VXy5NU1LCXmEcZfew4ezSu
law_summary.py は3本の生成系で唯一、モデルに法令のテキストを一切渡して いなかった。法令名・番号・分類・改正回数だけを渡し、答えの全ての語が モデルの記憶から出ていた。検証器は既知の1事故(廃止刑名)しか止めない。 根拠として渡すもの: 目次(編章節の見出し)+ 各条の見出し + 第一条の全文。 全文は渡さない(民法は 226,000 字で、どんな価格でもプロンプトに入らない)。 条見出しは民法で 1,250 件・17,823 字あり、その法律が何を扱うかの一覧その ものとして最も密度が高い。第一条は目的・適用範囲の規定なので scope の直接の 根拠になる — 刑法第一条は「日本国内において罪を犯したすべての者に適用する」 であり、それ自体が答えである。 検証器を2層に分ける: - validate_summary_shape: 形と廃止刑名。公開済みデータにも常時かけられる (data/raw は gitignore なので CI にスナップショットが存在しない) - validate_summary(summary, evidence): 上記に加え、キーワード全語が条文に 実在すること。散文は語で照合できない(「事業者」「日常生活」で偽陽性が 出る)が、キーワードは名詞なので機械判定できる。存在しない制度名は まずここに出る。evidence は必須引数 — 省略可能にすると、根拠を失った 呼び出し側が黙って元の記憶頼みの挙動に戻る。 証拠が空ならモデルを呼ばずに raise する(annotate.py と同じ契約)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VXy5NU1LCXmEcZfew4ezSu
民法・道路交通法・労働基準法・著作権法の /law ページには概要ブロックが 無かった(law_summary.py が一度も走っていなかった)。いずれも表示回数の 中心にある大物で、民法は直近2週間の検索表示の大半を集めている。 既存8件も根拠なしで書かれたものなので同時に作り直した。刑法の適用範囲は 「一定の場合には国外で犯罪を行った日本国民など」— これは第三条(国民の 国外犯)の話で、第一条とは別の条文だった。新しい文は第一条二項どおり 「日本国外にある日本船舶・日本航空機内において罪を犯した者」となっている。 道路交通法は生成が一度検証で止まっている(モデルが「駐車及び停車」と 書いたが、実際の章題は「停車及び駐車」)。何も保存せず exit 2 で終わり、 再実行で通った。検証器が想定どおり働いた実例。 test_shipped_data の summary チェックを「無ければスキップ」から 「無ければ失敗」に変えた。次に法令を追加したとき同じ穴が黙って再発しない。 Closes #14 Closes #16 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VXy5NU1LCXmEcZfew4ezSu
Gate3 の3層(Claude 構造化レビュー / intent-alignment / codex)が独立に 同じ根を指した。「根拠を渡す」と「"現行の"根拠を渡す」は別の要件で、 前者しか実装していなかった。 data/raw に入っているのは誰かが diff のために取った asof であって「今」では ない。実測すると 12法令中10法令が、施行済みの最新改正より古い条文を根拠に していた。刑法は全スナップショットが 2025-06-01 の拘禁刑統合より前で、根拠に 「懲役」「禁錮」が3回ずつ現れる一方、プロンプトはその語の使用を禁じていた — 根拠に忠実なモデルは検証で弾かれ、無視するモデルは記憶から書く。この検証器が 存在する理由そのものの法令で、その構造ができていた。 - main() は最新スナップショットが最終施行改正より古いとき exit 3(モデルを 呼ばない)。fetch すべきコマンドを出力する - load_evidence は未来日付のスナップショットを使わない(パイプラインは diff の ために施行前の日付を日常的に取得する)。最新候補が壊れていたら古い版へ フォールバックせず raise する — 成功に見えて旧法を現行法として公開するため - 空根拠ガードが機能していなかった。空文字の見出しノードが1つあるだけでリストは truthy になり、「## 目次」というラベルだけを根拠としてモデルを呼んでいた。 組み立て後の本文で判定する - e-Gov の XML は TOC 要素で目次を重複して持っており、本文側と両方を集めていた (民法で364行中197行が重複)。SKIP_SUBTREES に TOC を追加 - articles[0] を検証なしに「第一条」と呼んでいた。Num を確認し、違えば実際の 番号でラベルする - 1文字のキーワードを弾く。「刑」は「刑罰」の中にマッチするため根拠照合を すり抜ける。刑法が実際にそれを出荷しかけた - 全12法令の現行条文を取得し直して再生成 テストは変異検算済み(各ガードを1つずつ壊して赤を確認)。空根拠ガードのみ 二重防御のため、両方を外したときに赤になる。 CLAUDE.md の AI 生成の節は law_summary.py を「根拠なしの例外」と書いたままだった (#16 が閉じたので事実と逆)。現行の2層検証と「根拠の鮮度」の要件に更新。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VXy5NU1LCXmEcZfew4ezSu
ラウンド1で入れた鮮度ガードは fail-open だった。最新スナップショットを <law_id>_revisions.json の最終施行改正と比べていたが、その改正一覧自体の鮮度は 誰も保証していない。timeline.py は既存ファイルを無期限に再利用するので、条文と 改正一覧が揃って古い通常ケースでは used >= latest が成立して素通りする。欠損・ 不正形式・空リストはいずれも None になり、これも素通りだった。 同じ根で2ラウンド連続の指摘なので、この機械が何を買っているのかを問い直した。 答えはほとんど何も、だったので比較ごと捨てる。当日取得の条文だけを現行と認めれば 信用すべき2つ目のファイルが無くなる。fetch.py は数秒で、概要の生成は稀にしか 走らない。既存の12法令は取得時点で現行だったため再生成はしていない。 - main() は最新スナップショットが当日でなければ exit 3(fetch コマンドを提示) - 「今日」は Asia/Tokyo。日本の施行日を UTC ランナーで判定すると、日本時間の 深夜0時から9時が前日扱いになり、その日施行の改正を落とす - latest_enforced_revision を削除 - ファイル名は YYYY-MM-DD の厳密一致のみ候補にする。<law_id>_2025-01-01_copy.json は正規ファイルより後にソートされ、日付でない文字列のまま比較されていた - 空根拠ガードの残穴: ArticleTitle だけの Article は extract_text が「第一条」を 返し、ラベルだけで検査を通っていた。Sentence の存在を構造的に確認する - 第一条が無い法令では、その条を scope の根拠と断定しないよう根拠に注記する。 ラベルを直しただけでは、定義規定を適用範囲として読ませる問題が残っていた - 1文字キーワード拒否は grounding ではなく出力品質のヒューリスティックである旨を 明記(「の罪」のような部分文字列は依然通る) - テスト fixture が実データの構造とずれていた(Paragraph に Sentence が無い) 変異検算済み: 当日必須 / ファイル名厳密判定 / 構造的実質判定 / 第一条の注記 を それぞれ単独で壊して赤を確認し、復元して緑。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VXy5NU1LCXmEcZfew4ezSu
ディスク上のファイルから「この条文は現行か」を判定する試みが2ラウンド続けて critical を出した。原因は毎回同じで、ディスク上のどこにも「いつ取得したか」が 記録されていないこと。 - 改正一覧との比較は、鮮度を誰も保証しない2つ目のファイルを信用していた - 最新 asof が当日かの判定は、ファイル名しか見ていない。asof は「いつ時点の 法令か」であって「いつ取得したか」ではない。実データで 129AC0000000089_2026-04-01.json は 2026-03-26 に作られており、4月1日には 「当日のもの」として通っていた 同じ根で3ラウンド目なので、判定する仕組みを足すのをやめ、問い自体を消した。 生成と同じ呼び出しの中で fetch する。取得直後の条文を使うので、鮮度を推測する 必要がない。spec で決めた「fetch は law_summary から呼ばない」という責務分離を 捨てる判断(ユーザー裁定済み)— その分離こそが穴の出どころだった。 - fetch_evidence() が e-Gov から当日の条文を取得し、data/raw に保存して根拠を 組み立てる。取得失敗は raise(古いファイルへのフォールバックはしない。それが この問題の原因そのもの) - load_evidence / newest_snapshot / snapshot_date / SNAPSHOT_RE を削除 - モデルの応答中に JST の日付が変わったら保存しない。生成は日付をまたぐ長さが ありうる - 空の <Sentence/> だけを持つ条文を根拠と認めない(切り詰められた API 応答の形。 タグの存在だけを見ていたため「第一条」というラベルが本文として通っていた) - テストが実際に API を叩いていた。fetch_law_data をモックする 変異検算済み: API応答の検査 / 空Sentenceの排除 / 日付またぎの保存拒否 / 取得結果の保存 をそれぞれ単独で壊して赤、復元して緑。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VXy5NU1LCXmEcZfew4ezSu
完成差分への再レビューで、fetch を生成に組み込んだこと自体が生んだ穴が2つ出た。 1. data/raw への書き込みが応答の検証より先だった。truthy だが中身の無い law_full_text が返ると、保存してから build_evidence が失敗する。data/raw は diff パイプラインと共有しており diff.py がこれを読むので、概要生成の失敗が 別パイプラインの入力データの破壊になっていた。検証を先に済ませ、一時ファイル から os.replace で原子的に置き換える。 2. 第一条の本文が空でも、章見出しや条見出しが1つ残っていれば根拠が非空になり 生成が続いていた。プロンプトは「根拠に第一条を含む」と説明したままなので、 適用範囲の根拠が無いまま scope を書かせることになる。条が存在するのに非空の Sentence が無い場合は、見出しの有無によらず失敗させる。第一条そのものが無い 法令(条が0件)とは別の状況で、安全に続行できるのは後者だけ。 モジュール docstring が削除済みの「改正一覧との比較」を説明したままだったのも 修正(CLAUDE.md は既に現行の設計に一致している)。 変異検算済み: 空条文で失敗させる / 検証してから書く順序 を単独で壊して赤、 復元して緑。テストはレビュアーが示した経路をそのまま写した。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VXy5NU1LCXmEcZfew4ezSu
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This was referenced Sep 9, 2026
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
何を直すか
3本ある生成系(
annotate.py/explainer.py/law_summary.py)のうち、law_summary.pyだけがモデルに法令のテキストを渡していなかった。渡していたのは法令名・番号・分類・改正回数の4つだけで、答えの全ての語がモデルの記憶から出ていた(#16)。検証器は2025年に廃止された刑名を1件弾くだけで、誤ったスコープや別の廃止済み制度は素通りする。あわせて、概要ブロックが無かった4法令(民法・道路交通法・労働基準法・著作権法 — いずれも検索表示の中心にある大物)を埋めた(#14)。
設計
モデルに渡す根拠は 目次(編章節の見出し)+ 各条の見出し + 第一条の全文。全文は渡さない(民法は226,000字)。条見出しは民法で1,090件・15,467字あり、その法律が何を扱うかの一覧として最も密度が高い。第一条は目的・適用範囲の規定なので
scopeの直接の根拠になる — 刑法第一条は「日本国内において罪を犯したすべての者に適用する」で、それ自体が答えである。検証は2層。公開データに CI で常時かけられる層(形+廃止刑名)と、生成時にしかかけられない層(キーワード全語が根拠に実在すること)を分けた。
data/rawは gitignore なので CI にスナップショットが存在せず、根拠照合ができないため。散文の語レベル照合はしない(「事業者」「日常生活」で偽陽性が出る)。根拠は生成と同じ呼び出しの中で取得する。 ディスク上のファイルから「この条文は現行か」を判定する試みは Gate3 で2度失敗した。原因は毎回同じで、ディスク上のどこにも「いつ取得したか」が記録されていないこと —
asofは「いつ時点の法令か」であって「いつ取得したか」ではなく、実データで129AC0000000089_2026-04-01.jsonは 2026-03-26 に作られていた。取ったその場で使えば、鮮度を推測する必要がない。Gate3 で見つかった、この変更なしでは起きていた事故
12法令中10法令が、施行済みの最新改正より古い条文を根拠にしていた。
data/rawにあるのは誰かが diff のために取った日付であって「今」ではない。刑法にいたっては全スナップショットが 2025-06-01 の拘禁刑統合より前で、根拠に「懲役」「禁錮」が3回ずつ現れる一方、プロンプトはその語の使用を禁じていた。根拠に忠実なモデルは検証で弾かれ、無視するモデルは記憶から書く — この検証器が存在する理由そのものの法令で、その構造ができていた。検証
uv run pytest -q→ 108 passed(87 → 108)npm run lint→ 0 errors /npm run build→ 成功、12法令すべてに概要ブロックが出力Gate3
3ラウンド+完成差分への再レビュー1回。critical は各ラウンド 1 / 1 / 3 / 2 で、ラウンド1〜3は根が同一(渡す条文が現行か)だったため、CLAUDE.md の規律に従って実装を止めてユーザーに上げ、判定する仕組みを足すのをやめて設計を変えた。レビュアーは Claude 構造化レビュー・intent-alignment(fresh subagent)・codex(異族)。
最終確認で出た critical 2件(不正な API 応答が既存スナップショットを破壊する/第一条の本文が空でも生成が続く)は修正し、変異検算で閉じた。この最後の修正自体は外部レビューを受けていない(規律上、修正は周回ではなく検算で閉じる)。
Closes #14
Closes #16
🤖 Generated with Claude Code
https://claude.ai/code/session_01VXy5NU1LCXmEcZfew4ezSu