はじめに
選挙期間にブロードリスニングやgithub issueによる社会課題の管理などの話題をニュースなどで見聞きする機会があり、少しデータ管理について考えてみたくなったのでやってみました(社会的意義は全くなく単なるレクリエーションのようなものです)
本記事で行ったことは以下
- github issueを構造化してみる
- 社会課題、政策やマニフェストまで含めてデータ構造化する
- 構造化データをもとにAPI化
- issueの管理について考えてみる
なお、構造化の過程はGithubのissueを参考に考え始めてみる.
なお本記事で利用する社会問題issueおよび政策policyは架空のものですが, 実際のマニフェストがどういったものかは以下の記事をみてイメージを掴みました
github Issue
AIを使ってgithub issueを構造化するとしたらどういったスキーマになるかを生成した
{
"@context": {
"@vocab": "https://schema.org/",
"additional": "https://github.co.jp/"
},
"@type": "CreativeWork",
"@id": "#issue-12345abcde",
"name": "API returns 500 error when request is invalid",
"description": "When an invalid parameter is passed, the API should return 400 Bad Request but returns 500 Internal Server Error instead.",
"identifier": 123,
"url": "https://github.com/owner/repo/issues/123",
"creator": {
"@type": "Person",
"name": "alice",
"url": "https://github.com/alice"
},
"dateCreated": "2025-07-21T10:00:00Z",
"datePublished": "2025-07-21T10:00:00Z",
"dateModified": "2025-07-21T14:30:00Z",
"keywords": [
"bug",
"API",
"error"
],
"interactionStatistic": [
{
"@type": "InteractionCounter",
"interactionType": {
"@type": "CommentAction"
},
"userInteractionCount": 3
},
{
"@type": "InteractionCounter",
"interactionType": {
"@type": "LikeAction"
},
"userInteractionCount": 2
}
],
"comment": [
{
"@type": "Comment",
"identifier": 1,
"text": "I am also seeing this issue on v2.0.",
"dateCreated": "2025-07-21T11:00:00Z",
"creator": {
"@type": "Person",
"name": "bob",
"url": "https://github.com/bob"
}
},
{
"@type": "Comment",
"identifier": 2,
"text": "Working on a fix, will open a PR soon.",
"dateCreated": "2025-07-21T12:15:00Z",
"creator": {
"@type": "Person",
"name": "carol",
"url": "https://github.com/carol"
}
}
],
"additional:status": "open",
"additional:assignee": {
"@type": "Person",
"name": "carol",
"url": "https://github.com/carol"
},
"additional:repository": {
"@type": "SoftwareSourceCode",
"name": "example-repo",
"url": "https://github.com/owner/repo"
},
"additional:milestone": "v2.1",
"additional:labels": [
"bug",
"high priority"
]
}
社会問題を扱うためのissueとして定義した場合
今回定義するのは社会issueなので, 実際のgithub Issue機能やスキーマからいくつか修正を加えています
- repositoryについてソースコードに紐づけるわけではないため削除
- 作成はcreatorからpublisherに変更
- keywordsは削除
- labelは削除
- 人にアサインするものではないのでassigneeをaddressedByに変更
- citationを追加
- date系のプロパティを追加
github actionなどのワークフローで変換できそうです
{
"@context": {
"@vocab": "https://schema.org/",
"ex": "https://github.co.jp/"
},
"@type": "Statement",
"@id": "#issue-edu-001",
"name": "ソフトウェアエンジニア不足の解消",
"description": "コードがかけるソフトウェアエンジニア人材が不足している",
"identifier": "edu-001",
"url": "https://example.com/",
"dateCreated": "2025-07-21T10:00:00Z",
"datePublished": "2025-07-21T10:00:00Z",
"dateModified": "2025-07-21T10:00:00Z",
"publisher": {
"@id": "#org-XXX",
"@type": "Organization",
"name": "XXX省"
},
"about": [
{
"@type": "Legislation",
"name": "XX法",
"url": "https://example.com/"
}
],
"citation": "https://example.com/",
"interactionStatistic": [],
"comment": [],
"additional:status": "open",
"additional:addressedBy": [
{
"@id": "#policy-edu-001"
}
]
}
API化
問題(issue)に対して政策(policy)が言及した場合には addressedBy として関連付けされます
マニフェスト(manifesto)は政策(policy)を束ねたものとして紐付けることでissueからマニフェストまでが関連付けされます
API化してみます.
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
app = FastAPI()
# ======= JSONデータ(サンプルとして埋め込み) =======
data = {
"@graph": [
{
"@id": "#party-a",
"@type": "PoliticalParty",
"name": "〇〇党",
"alternateName": "まるまる党",
"url": "https://example.com/party-a",
"logo": {
"@type": "ImageObject",
"url": "https://example.com/party-a-logo.png",
"caption": "〇〇党のロゴ"
},
"leader": {"@id": "#person-1"},
"member": [{"@id": "#person-1"}, {"@id": "#person-2"}]
},
{
"@type": "Statement",
"@id": "#issue-edu-001",
"name": "ソフトウェアエンジニア不足",
"description": "コードがかけるソフトウェアエンジニア人材が不足している",
"identifier": "edu-001",
"url": "https://example.com/",
"dateCreated": "2025-07-21T10:00:00Z",
"datePublished": "2025-07-21T10:00:00Z",
"dateModified": "2025-07-21T10:00:00Z",
"publisher": {"@id": "#org-XXX", "@type": "Organization", "name": "XXX省"},
"about": [{"@type": "Legislation", "name": "XX法", "url": "https://example.com/"}],
"citation": "https://example.com/",
"interactionStatistic": [],
"comment": [],
"additional:status": "open",
"additional:addressedBy": [{"@id": "#policy-edu-001"}]
},
{
"@id": "#policy-edu-001",
"@type": "Statement",
"name": "Qiitaで就活",
"description": "大学生はQiitaに自分の作ったソフトウェアをコード付きで投稿したら企業からオファーが来る",
"dateCreated": "2025-07-21T10:00:00Z",
"dateModified": "2025-07-21T10:00:00Z",
"datePublished": "2025-07-21T10:00:00Z",
"isPartOf": {"@id": "#manifesto-2025"},
"about": [{"@id": "#issue-edu-001"}],
"additional:influencesLegislation": {"@id": "#legislation-edu-001"},
},
{
"@id": "#manifesto-2025",
"@type": "Statement",
"name": "XXXX年選挙公約",
"description": "XXXX年の重点公約集です。",
"dateCreated": "",
"dateModified": "",
"datePublished": "",
"publisher": {"@id": "#party-a"},
"hasPart": [
{"@id": "#policy-economy-001"},
{"@id": "#policy-social-001"},
{"@id": "#policy-agri-001"},
{"@id": "#policy-edu-001"}
],
},
]
}
graph = data["@graph"]
items_by_id = {item["@id"]: item for item in graph}
# ✅ 政党ID -> 最新マニフェストを1件だけ
@app.get("/party/{party_id}/manifesto")
async def get_manifesto_by_party(party_id: str):
manifestos = [
item for item in graph
if item["@type"] == "Statement"
and item.get("publisher", {}).get("@id") == f"#{party_id}"
]
if not manifestos:
raise HTTPException(status_code=404, detail="Manifesto not found for party.")
# 最新を1件(想定: 日付ソートが必要なら追加で)
return JSONResponse(content={"manifesto": manifestos[-1]})
# ✅ マニフェストID -> 含まれる政策 + 紐づくissue を多対多で
@app.get("/manifesto/{manifesto_id}/policies")
async def get_policies_by_manifesto(manifesto_id: str):
manifesto = items_by_id.get(f"#{manifesto_id}")
if not manifesto:
raise HTTPException(status_code=404, detail="Manifesto not found.")
policy_refs = manifesto.get("hasPart", [])
policies_data = []
for ref in policy_refs:
policy = items_by_id.get(ref["@id"])
if policy:
# policy が参照している issue を収集
issue_list = []
for about in policy.get("about", []):
issue = items_by_id.get(about["@id"])
if issue:
issue_list.append(issue)
policies_data.append({
"policy": policy,
"issues": issue_list
})
return JSONResponse(content={"manifesto_id": manifesto_id, "policies": policies_data})
# ✅ issueID -> 紐づく全ての政策
@app.get("/issue/{issue_id}/policies")
async def get_policies_by_issue(issue_id: str):
issue = items_by_id.get(f"#{issue_id}")
if not issue:
raise HTTPException(status_code=404, detail="Issue not found.")
addressed_by = issue.get("additional:addressedBy", [])
policies = []
for ref in addressed_by:
policy = items_by_id.get(ref["@id"])
if policy:
policies.append(policy)
return JSONResponse(content={
"issue_id": issue_id,
"issue": issue,
"policies": policies
})
issueの構造データを元に,マニフェストまで構造化してデータ管理することであっという間に生成や修正ができそうです.
課題(issue)や政策(policy)を管理するには
issueをクローズするために外部要因が改善をしてくれない前提とすると, pull requestを出して改善をするしかない.政策立案、法案提出、施行〜改善レビューまでの一連の流れをActionとして構造化し、pull requestに対応づけるのが良さそうに感じました.
政策(Policy)はタスク管理、マニフェスト(manifesto)はマイルストーン定義のようなものにちかそうと感じました.
Actionに入った場合にはタスクのスコープがきちんと明確になっていないと以降の工程全てに影響してしまう. 逆にありえないスコープのタスクを選定してしまうとレビューがしっかり回らなくなります.
事前の内容からタスクのスコープ修正が入ることをしっかり許容しつつ、レビューまでの工程がしっかり回ることで大なり小なりissueに対して影響を与えることが大事に感じました.