Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
849 changes: 849 additions & 0 deletions docs/superpowers/plans/2026-06-09-law-explainer-seo.md

Large diffs are not rendered by default.

172 changes: 172 additions & 0 deletions docs/superpowers/specs/2026-06-09-law-explainer-seo-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# 設計: `/law` ページへの「最近の改正をわかりやすく」解説の追加(SEO強化 B案)

- 日付: 2026-06-09
- 関連: GSC/GA4分析(imp急増・CTRほぼ0、`/law`改正履歴ページが勝ち型)、canonical修正(commit c83dd62)の後続施策

## 背景・目的

GSC分析の結論:
- 表示回数(imp)は4月~25/日 → 6月~60-93/日へ急増しているが、クリックはほぼ0(CTR最適化が最大レバレッジ)。
- `/law/{lawId}`(改正履歴)ページが唯一クリックを獲得(「不動産登記法改正 履歴」で順位3.5・CTR15%)。`/diff/`より検索意図に合致。
- 「○○法改正 わかりやすく」「○○法 改正 履歴」が有望クエリ。「不動産登記法 改正 わかりやすく」は順位45=未対応。

**目的**:`/law`ページに「最近の改正をわかりやすく」長文解説を追加し、上がってきた露出を実クリックに変える。検索意図=「最近この法律はどう変わったか」を平易に説明する。

## スコープ

- **Phase 1(本spec)**:実績上位4法令のみ生成・検証する。
- 不動産登記法 `416AC0000000123`(順位3.5・クリック実績)
- 民法 `129AC0000000089`(858impの最大露出源)
- 労働基準法 `322AC0000000049`(改正履歴で露出開始)
- 道路交通法 `335AC0000000105`(wwwで露出)
- **対象外(将来)**:残り法令への横展開はPhase1の効果をGSCで確認後に判断。

## アーキテクチャ方針

既存の `law_summary.py`(Claudeで`summary`を生成→timeline JSONに格納→`/law`ページで描画)と同一の疎結合パターンを踏襲する。データ生成(Python/Claude)とレンダリング(Next.js)は分離。

```
timeline.py → data/timelines/{law_id}.json
→ explainer.py (Claude, 新規) → 同JSONに explainer フィールド追記
→ frontend/public/data/timelines/{law_id}.json
→ /law/[lawId]/page.tsx が描画
```

## データ構造

`lib/types.ts` の `LawTimeline` に optional フィールドを追加(**未生成の法令は従来表示のまま=後方互換**)。

```ts
export interface ExplainerChange {
year: string; // 例: "2024"
title: string; // 改正の通称。例: "相続登記の義務化"
what: string; // 何が変わったか(平易な1-3文)
why?: string; // なぜ変わったか(背景)。根拠(diff/pr_summary)がある場合のみ生成。無ければ省略
impact?: string; // 私たちへの影響。根拠がある場合のみ生成。無ければ省略
grounded: boolean; // 条文差分(pr_summary)に基づくか。falseは施行日・改正法名のみ確認済みの軽量エントリ
}

export interface ExplainerFaq {
q: string;
a: string;
}

export interface LawExplainer {
intro: string; // 導入2-3文
recent_changes: ExplainerChange[]; // 最近の主要改正 2-4件(改正法/トピック単位に集約)
faq: ExplainerFaq[]; // よくある質問 0-4件(欠落/null時は[]に正規化)
}

export interface LawTimeline {
// ...既存フィールド
explainer?: LawExplainer;
}
```

JSON例:
```jsonc
"explainer": {
"intro": "不動産登記法は近年、相続登記の義務化など大きな改正が続いています。…",
"recent_changes": [
{
"year": "2024",
"title": "相続登記の義務化",
"what": "相続を知った日から3年以内の登記が必須に。…",
"why": "所有者不明土地の増加が社会問題化したため。…",
"impact": "相続人は期限内の手続きが必要。怠ると過料。…"
}
],
"faq": [
{ "q": "相続登記の義務化はいつから?", "a": "2024年4月1日から施行されています。" }
]
}
```

## レンダリング

- 新コンポーネント `components/law-explainer.tsx`(page.tsx肥大化防止)。
- 配置:`/law/[lawId]/page.tsx` の「この法律について」(`summary`)カードの直後。
- 既存のTailwind/カードスタイル(`border`/`rounded-lg`/`bg-[var(--muted)]`/Iconコンポーネント)を踏襲。
- 構成:
- `<h2>{law_title}の最近の改正をわかりやすく</h2>`(「○○法改正 わかりやすく」クエリに直撃する見出し)
- `intro` 段落
- `recent_changes` を「年・タイトル」見出し+「何が変わった?」を表示。`why`/`impact`は値がある場合のみ表示(`grounded:false`では省略される)
- `faq` を Q&A リストで表示(`faq`が空配列なら非表示)
- AI生成の明示ラベル(about方針との一貫性)
- `explainer` が undefined の法令ではセクション自体を描画しない。

### meta description

- `/law/[lawId]/page.tsx` の `generateMetadata` で、`explainer.intro` があればそれを description に優先採用(無ければ従来通り `summary.description`)。「最近の改正」「わかりやすく」がSERP説明文に載るようにする。
- title は前コミット `c83dd62` で対応済み(`{法令名}の改正履歴|全N回の改正一覧`)。本specでは変更しない。

### 構造化データ(優先度・低)

- **FAQリッチリザルトは政府機関・医療系の権威サイト限定で、lexdiffは対象外**。よってFAQPage JSON-LDはCTR改善の主要施策とせず、Phase1の成功条件・検証項目には含めない。
- 実装としては、`faq`が1件以上ある場合に `FAQPage` JSON-LD を出力すること自体は任意(セマンティック補助)。Phase1では**スコープ外**とし、FAQ本文(オンページのテキスト)のみ提供する。将来必要になれば `components/faq-jsonld.tsx` で追加。

## 生成スクリプト `scripts/explainer.py`

- `law_summary.py` を踏襲(`load_env`、`data/timelines/{law_id}.json`読み込み、生成、`data/` と `frontend/public/data/` の両方に書き戻し)。

### 入力の集約と根拠付け(hallucination対策の中核)

timelineをそのまま渡さない。以下の前処理を行う:

1. **改正法/トピック単位に集約**:同一 `amendment_law_title` が複数の施行日で並ぶケースや、技術的な整理法(「○○法の施行に伴う関係法律の整理等に関する法律」等)を1件に畳む。`diff_id:null`が大半なので、件数ではなく「市民に意味のある改正」を2-4件選ぶ。
2. **diff有改正を優先&根拠付与**:`diff_id` のあるエントリは対応する `data/diffs/{diff_id}.json`(または `frontend/public/data/{diff_id}.json`)の `pr_summary`(title/summary/background/impact/key_changes)を入力に含め、`grounded:true` とする。これらは `why`/`impact` を生成してよい。
3. **diff無改正は抑制**:`diff_id:null` のエントリは `amendment_law_title`・`enforcement_date` のみ確認済みとして扱い、`grounded:false`。`what` は改正法名から言える範囲に留め、**`why`/`impact` は生成しない(省略)**。
4. recent_changes は新しい施行日順。`grounded:true`(実質的改正)を優先的に採用する。

- 入力プロンプト材料:`law_title`、`law_num`、`category`、`summary.description`、上記で集約・根拠付けした改正リスト。
- 出力:`explainer` JSON(上記スキーマ)。コードフェンス除去・`json.loads`は`law_summary.py`と同じ堅牢化を流用。`faq`欠落/`null`は`[]`に、`why`/`impact`の空文字は未設定として正規化。
- **モデル**:`claude-sonnet-4-6`(最新Sonnet。長文品質とコストのバランス。既存`law_summary.py`の`claude-sonnet-4-20250514`から更新)。
- **プロンプト方針**:日本語SEO記事の作法に沿う。
- 見出し・本文に検索意図の語(「○○法 改正」「わかりやすく」「いつから」)を自然に含める。
- 専門用語を避け日常生活との関わりで説明(既存summaryの方針と一貫)。
- **提供データに無い事実(背景・影響・数値・期限)は断定しない**。`grounded:false` の改正では背景/影響を書かない。不確実な点は書かない(書かない方を選ぶ)。
- 各フィールドの文字数目安:intro 80-150字、what 40-100字、why/impact 各40-100字、faq.a 40-120字。
- 既存の `requirements.txt` / `pyproject.toml`(anthropic)で追加依存なし。

## テスト・検証

### 自動チェック(機械的に落とす)
`explainer.py` 内(または小スクリプト)で生成JSONを検証し、満たさなければエラー終了:
- 必須キー(`intro`/`recent_changes`/`faq`)の存在と型。
- `recent_changes` は1-4件、各要素に `year`/`title`/`what`/`grounded`。`grounded:false` の要素は `why`/`impact` を持たないこと。
- 文字列長レンジ(intro/what/why/impact/faq)。空文字・`null`の混入なし(`faq`欠落は`[]`に正規化済み前提)。
- `year` が4桁数字、対象法令の施行年と矛盾しない(timelineの年集合に含まれる)。

### 事実性レビューゲート(公開前必須・手動)
- Phase1の4法令は**1件ずつ人手でレビュー**し、各 `recent_changes`/`faq` を次の根拠と突合:施行日・改正法名(timeline)、`grounded:true`は `pr_summary`。
- 突合できない断定(数値・期限・因果)が1つでもあれば、その記述を削るか再生成する。**未確認の記述を残したまま公開しない**。
- AI生成の明示ラベルは既存about方針に準拠(免責は補助であって、公開可否の担保は上記レビューが負う)。

### ビルド・互換
- `npm run build` がエラーなく通る。
- ビルド出力 `out/law/{id}.html` に `<h2>…わかりやすく</h2>` と本文が含まれることを確認。
- `explainer` 未生成の法令ページが従来通り描画される(後方互換)ことを確認。

## 成功条件・観測(Phase1)

- **観測期間**:本番反映後 約2-3週間(Googleの再クロール・再評価を待つ)。GSC/GA4はmy-mcp-server(GSC alias `lexdiff`, GA4 property 528978508)で取得。
- **主要指標**(4法令の `/law` ページに限定して観測):
- クリック数・CTR(最重要。現状ほぼ0からの改善)。
- 「○○法 改正 わかりやすく」「○○法 改正 履歴」等クエリの掲載順位とクリック有無。
- 表示回数(imp)とインデックス状況。
- **判断基準**:4法令は母数が小さくCTRが揺れやすいため、CTR単独でなく「クリック数の増加」と「対象クエリでの順位上昇(特に2ページ目→1ページ目)」を併せて見る。明確な改善が見えれば残り法令へ横展開、横ばいならプロンプト/構成を見直す。
- FAQリッチリザルトはlexdiffが対象外のため**成功条件に含めない**。

## 非対象(YAGNI)

- 全法令への一括生成(Phase1の効果確認後)。
- `/diff` ページ側のコンテンツ追加。
- FAQPage JSON-LD(lexdiffはリッチリザルト対象外。将来必要時に追加)。
- `recent_changes`/`faq` ごとの確認者・確認日・公開可否などのprovenance管理(Phase1は4法令を手動レビューするため過剰)。
- 多言語・履歴差分の自動更新ワークフロー。

## 影響ファイル

- 新規: `scripts/explainer.py`, `frontend/components/law-explainer.tsx`
- 変更: `frontend/lib/types.ts`, `frontend/app/law/[lawId]/page.tsx`(`law-explainer`描画+`generateMetadata`のdescriptionを`explainer.intro`優先に)
- データ: `data/timelines/{4法令}.json` と `frontend/public/data/timelines/{4法令}.json`
6 changes: 6 additions & 0 deletions frontend/app/law/[lawId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Icon } from "@/components/icon";
import { OpenGikaiLinks } from "@/components/opengikai-link";
import { getThemesForLaw } from "@/lib/life-themes";
import { BreadcrumbJsonLd } from "@/components/breadcrumb-jsonld";
import { LawExplainerSection } from "@/components/law-explainer";

export function generateStaticParams() {
return getTimelineIds().map((lawId) => ({ lawId }));
Expand All @@ -19,6 +20,7 @@ export async function generateMetadata({
const { lawId } = await params;
const data = getTimelineData(lawId);
const desc =
data.explainer?.intro ||
data.summary?.description ||
`${data.law_title}の改正履歴を時系列で一覧。全${data.revision_count}回の改正について、いつ・どの条文が・どう変わったかをわかりやすく確認できます。`;
return {
Expand Down Expand Up @@ -152,6 +154,10 @@ export default async function LawPage({
</div>
)}

{data.explainer && (
<LawExplainerSection lawTitle={data.law_title} explainer={data.explainer} />
)}

<div className="flex flex-col lg:flex-row gap-6">
{/* Git log — main content */}
<div className="flex-1 min-w-0">
Expand Down
75 changes: 75 additions & 0 deletions frontend/components/law-explainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Icon } from "@/components/icon";
import type { LawExplainer } from "@/lib/types";

export function LawExplainerSection({
lawTitle,
explainer,
}: {
lawTitle: string;
explainer: LawExplainer;
}) {
return (
<section className="border border-[var(--border)] rounded-lg overflow-hidden">
<div className="px-4 py-3 bg-[var(--muted)] border-b border-[var(--border)] flex items-center gap-2">
<Icon name="history" size={16} className="opacity-40" />
<h2 className="text-[15px] font-medium">
{lawTitle}の最近の改正をわかりやすく
</h2>
<span className="text-[11px] opacity-40 ml-auto">AI要約</span>
</div>
<div className="px-4 py-4 space-y-5">
<p className="text-[15px] leading-[26px]">{explainer.intro}</p>

<div className="space-y-4">
{explainer.recent_changes.map((c, i) => (
<div
key={i}
className="border-t border-[var(--border)] pt-4 first:border-t-0 first:pt-0"
>
<div className="flex items-baseline gap-2 mb-2">
<span className="font-mono text-[13px] text-[var(--diff-hunk-text)]">
{c.year}
</span>
<h3 className="text-[15px] font-bold">{c.title}</h3>
</div>
<dl className="space-y-1.5 text-[14px] leading-[24px]">
<div>
<dt className="inline font-medium">何が変わった? </dt>
<dd className="inline opacity-70">{c.what}</dd>
</div>
{c.why && (
<div>
<dt className="inline font-medium">なぜ変わった? </dt>
<dd className="inline opacity-70">{c.why}</dd>
</div>
)}
{c.impact && (
<div>
<dt className="inline font-medium">私たちへの影響 </dt>
<dd className="inline opacity-70">{c.impact}</dd>
</div>
)}
</dl>
</div>
))}
</div>

{explainer.faq.length > 0 && (
<div className="pt-2">
<h3 className="text-[14px] font-medium mb-3">よくある質問</h3>
<div className="space-y-3">
{explainer.faq.map((f, i) => (
<div key={i}>
<p className="text-[14px] font-bold">Q. {f.q}</p>
<p className="text-[14px] leading-[24px] opacity-70 mt-0.5">
A. {f.a}
</p>
</div>
))}
</div>
</div>
)}
</div>
</section>
);
}
21 changes: 21 additions & 0 deletions frontend/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,26 @@ export interface LawSummary {
keywords: string[];
}

export interface ExplainerChange {
year: string;
title: string;
what: string;
why?: string;
impact?: string;
grounded: boolean;
}

export interface ExplainerFaq {
q: string;
a: string;
}

export interface LawExplainer {
intro: string;
recent_changes: ExplainerChange[];
faq: ExplainerFaq[];
}

export interface Contributor {
name: string;
position: string;
Expand All @@ -113,4 +133,5 @@ export interface LawTimeline {
summary?: LawSummary;
category?: string;
contributors?: Contributor[];
explainer?: LawExplainer;
}
47 changes: 46 additions & 1 deletion frontend/public/data/timelines/129AC0000000089.json
Original file line number Diff line number Diff line change
Expand Up @@ -529,5 +529,50 @@
"committee": "内閣委員会"
}
}
]
],
"explainer": {
"intro": "民法は近年、家族のあり方や財産に関するルールを時代の変化に合わせて見直す改正が続いています。離婚後の親権制度の大きな変更や、デジタル化への対応など、私たちの日常生活に直結する改正が相次いでいます。",
"recent_changes": [
{
"year": "2026",
"title": "離婚後の共同親権制度の導入",
"what": "離婚後も父母の両方が親権者になれる「共同親権」制度が導入されます。また、財産分与の請求期間が2年から5年に延長され、養育費の支払いを守る新たな仕組みも設けられます(2026年施行)。",
"grounded": true,
"why": "離婚件数の増加や子どもの貧困問題が深刻化するなか、子の利益を最優先にする家族法制への見直しが求められていました。男女平等の推進や国際的な制度との整合性を図る必要もありました。",
"impact": "離婚後も両親が子育てに関わりやすくなり、子どもの生活環境の安定が期待されます。財産分与の請求期間が延びることで、離婚後の生活再建に向けた準備の時間も増えます。"
},
{
"year": "2028",
"title": "民事手続きへのIT・デジタル技術の活用対応",
"what": "民事関係の手続きに情報通信技術(IT)を活用しやすくするため、民法の関連規定が整備されます(2028年施行)。",
"grounded": false
},
{
"year": "2027",
"title": "譲渡担保・所有権留保に関する新法への対応",
"what": "「譲渡担保契約」や「所有権留保契約」を定めた新しい法律の施行に合わせて、民法の関連規定が整備・調整されます(2027年施行)。",
"grounded": false
},
{
"year": "2025",
"title": "刑法改正に伴う民法関連規定の整理",
"what": "刑法などの改正に伴い、民法の関連する条文の表現や規定が整理・統一されます(2025年施行)。",
"grounded": false
}
],
"faq": [
{
"q": "共同親権はいつから始まりますか?",
"a": "2026年に施行予定です。離婚後の親権のルールが大きく変わりますので、離婚を検討中の方は事前に内容を確認しておくことをおすすめします。"
},
{
"q": "財産分与の請求期間が延びるのはいつからですか?",
"a": "2026年施行の民法改正により、離婚後の財産分与を請求できる期間がこれまでの2年から5年に延長される予定です。"
},
{
"q": "今回の民法改正は全員に関係しますか?",
"a": "すべての方に直接関係するわけではありませんが、離婚・子育て・財産・契約などに関わる方には影響が生じる場合があります。自分の状況に合わせて確認することをおすすめします。"
}
]
}
}
Loading
Loading