Update app.py
Browse files
app.py
CHANGED
|
@@ -1,10 +1,13 @@
|
|
| 1 |
import re
|
|
|
|
| 2 |
import requests
|
| 3 |
import gradio as gr
|
| 4 |
|
| 5 |
-
|
|
|
|
|
|
|
| 6 |
|
| 7 |
-
# よく使う法令
|
| 8 |
KNOWN = {
|
| 9 |
"民法": "429AC0000000089",
|
| 10 |
"会社法": "429AC0000000086",
|
|
@@ -13,8 +16,8 @@ KNOWN = {
|
|
| 13 |
"不動産登記法": "429AC0000000092",
|
| 14 |
}
|
| 15 |
|
| 16 |
-
# ざっくり漢数字変換(条番号用:1〜399くらいまでで十分)
|
| 17 |
KANJI_DIG = {0:"零",1:"一",2:"二",3:"三",4:"四",5:"五",6:"六",7:"七",8:"八",9:"九"}
|
|
|
|
| 18 |
def int_to_kanji(n:int)->str:
|
| 19 |
if n <= 10:
|
| 20 |
return ["零","一","二","三","四","五","六","七","八","九","十"][n]
|
|
@@ -32,80 +35,141 @@ def int_to_kanji(n:int)->str:
|
|
| 32 |
|
| 33 |
def parse_query(q:str):
|
| 34 |
q = q.strip().replace(" ","")
|
| 35 |
-
# 「
|
| 36 |
-
|
| 37 |
-
m = re.match(r"(?P<law>[^0-9条第第\s]+)\s*(第?\s*(?P<num>\d+)\s*条?)", q)
|
| 38 |
if not m:
|
| 39 |
-
# 例: 「民法3条」「民法 第3条」「民法3」
|
| 40 |
m = re.match(r"(?P<law>[^0-9\s]+)\s*(?P<num>\d+)", q)
|
| 41 |
if not m:
|
| 42 |
raise ValueError("「民法3条」「会社法10条」みたいに入力して。")
|
| 43 |
-
|
| 44 |
-
num = int(m.group("num"))
|
| 45 |
-
return law, num
|
| 46 |
|
| 47 |
def find_law_id(law_name:str)->str:
|
| 48 |
-
# まず内蔵マップ
|
| 49 |
if law_name in KNOWN:
|
| 50 |
return KNOWN[law_name]
|
| 51 |
-
# キーワード検索
|
| 52 |
-
|
| 53 |
-
r = requests.get(BASE_URL, params=params, timeout=15)
|
| 54 |
r.raise_for_status()
|
| 55 |
-
|
| 56 |
-
infos = data.get("lawListInfo", {}).get("lawInfo", [])
|
| 57 |
if not infos:
|
| 58 |
raise ValueError(f"法令が見つからない:「{law_name}」")
|
| 59 |
-
# 完全一致
|
| 60 |
for it in infos:
|
| 61 |
if it.get("lawName") == law_name:
|
| 62 |
return it.get("lawId")
|
| 63 |
return infos[0].get("lawId")
|
| 64 |
|
| 65 |
-
def
|
| 66 |
-
#
|
| 67 |
-
r = requests.get(
|
| 68 |
-
r.
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
try:
|
| 97 |
law, num = parse_query(q)
|
| 98 |
law_id = find_law_id(law)
|
| 99 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
except Exception as e:
|
| 101 |
return f"❌ エラー: {e}"
|
| 102 |
|
| 103 |
demo = gr.Interface(
|
| 104 |
-
fn=
|
| 105 |
-
inputs=gr.Textbox(label="例)民法3条 / 会社法10条 / 不動産登記法47条"),
|
| 106 |
-
outputs=gr.Textbox(label="結果", lines=
|
| 107 |
-
title="e-Gov 法令API v2|条文
|
| 108 |
-
description="法令名+条番号
|
| 109 |
)
|
| 110 |
|
| 111 |
demo.launch()
|
|
|
|
| 1 |
import re
|
| 2 |
+
import json
|
| 3 |
import requests
|
| 4 |
import gradio as gr
|
| 5 |
|
| 6 |
+
LIST_URL = "https://laws.e-gov.go.jp/api/2/laws"
|
| 7 |
+
GET_URL_A = "https://laws.e-gov.go.jp/api/2/laws/{lawId}"
|
| 8 |
+
GET_URL_B = "https://laws.e-gov.go.jp/api/2/laws" # ?lawId= フォールバック
|
| 9 |
|
| 10 |
+
# よく使う法令のID(即ヒット用)
|
| 11 |
KNOWN = {
|
| 12 |
"民法": "429AC0000000089",
|
| 13 |
"会社法": "429AC0000000086",
|
|
|
|
| 16 |
"不動産登記法": "429AC0000000092",
|
| 17 |
}
|
| 18 |
|
|
|
|
| 19 |
KANJI_DIG = {0:"零",1:"一",2:"二",3:"三",4:"四",5:"五",6:"六",7:"七",8:"八",9:"九"}
|
| 20 |
+
|
| 21 |
def int_to_kanji(n:int)->str:
|
| 22 |
if n <= 10:
|
| 23 |
return ["零","一","二","三","四","五","六","七","八","九","十"][n]
|
|
|
|
| 35 |
|
| 36 |
def parse_query(q:str):
|
| 37 |
q = q.strip().replace(" ","")
|
| 38 |
+
q = re.sub(r"(?<=\d)上$", "条", q) # 「3上」を補正
|
| 39 |
+
m = re.match(r"(?P<law>[^0-9条第\s]+)\s*(第?\s*(?P<num>\d+)\s*条?)", q)
|
|
|
|
| 40 |
if not m:
|
|
|
|
| 41 |
m = re.match(r"(?P<law>[^0-9\s]+)\s*(?P<num>\d+)", q)
|
| 42 |
if not m:
|
| 43 |
raise ValueError("「民法3条」「会社法10条」みたいに入力して。")
|
| 44 |
+
return m.group("law"), int(m.group("num"))
|
|
|
|
|
|
|
| 45 |
|
| 46 |
def find_law_id(law_name:str)->str:
|
|
|
|
| 47 |
if law_name in KNOWN:
|
| 48 |
return KNOWN[law_name]
|
| 49 |
+
# キーワード検索(一覧API)
|
| 50 |
+
r = requests.get(LIST_URL, params={"keyword": law_name, "num": 25}, timeout=20)
|
|
|
|
| 51 |
r.raise_for_status()
|
| 52 |
+
infos = r.json().get("lawListInfo", {}).get("lawInfo", [])
|
|
|
|
| 53 |
if not infos:
|
| 54 |
raise ValueError(f"法令が見つからない:「{law_name}」")
|
| 55 |
+
# 完全一致優先
|
| 56 |
for it in infos:
|
| 57 |
if it.get("lawName") == law_name:
|
| 58 |
return it.get("lawId")
|
| 59 |
return infos[0].get("lawId")
|
| 60 |
|
| 61 |
+
def fetch_law_json(law_id:str)->dict:
|
| 62 |
+
# 形式A: /laws/{lawId}
|
| 63 |
+
r = requests.get(GET_URL_A.format(lawId=law_id), timeout=30)
|
| 64 |
+
if r.status_code == 200:
|
| 65 |
+
return r.json()
|
| 66 |
+
# 形式B: /laws?lawId=...
|
| 67 |
+
r2 = requests.get(GET_URL_B, params={"lawId": law_id}, timeout=30)
|
| 68 |
+
r2.raise_for_status()
|
| 69 |
+
return r2.json()
|
| 70 |
+
|
| 71 |
+
def flatten_text(x):
|
| 72 |
+
"""dict/list/str をテキストにフラット化"""
|
| 73 |
+
if x is None:
|
| 74 |
+
return ""
|
| 75 |
+
if isinstance(x, str):
|
| 76 |
+
return x
|
| 77 |
+
if isinstance(x, list):
|
| 78 |
+
return "\n".join(flatten_text(i) for i in x)
|
| 79 |
+
if isinstance(x, dict):
|
| 80 |
+
parts = []
|
| 81 |
+
# よく出るキーを優先的に拾う
|
| 82 |
+
for k in ["paragraphSentence","itemSentence","supplProvisionLabel","chapterTitle","articleTitle","articleCaption","note"]:
|
| 83 |
+
if k in x:
|
| 84 |
+
parts.append(flatten_text(x[k]))
|
| 85 |
+
# その他のキーも一応辿る
|
| 86 |
+
for v in x.values():
|
| 87 |
+
parts.append(flatten_text(v))
|
| 88 |
+
return "\n".join(p for p in parts if p)
|
| 89 |
+
return ""
|
| 90 |
+
|
| 91 |
+
def find_article_blocks(obj, target_label):
|
| 92 |
+
"""
|
| 93 |
+
JSONツリーを再帰的に探索し、「第◯条」にマッチする塊(dict)を返す。
|
| 94 |
+
e-Gov v2 は Article が色々な入れ子に出現するので総当たりで探す。
|
| 95 |
+
"""
|
| 96 |
+
hits = []
|
| 97 |
+
|
| 98 |
+
if isinstance(obj, dict):
|
| 99 |
+
# Article相当の塊を検知
|
| 100 |
+
cand_title = ""
|
| 101 |
+
for key in ["articleNumber","articleTitle","ArticleNumber","ArticleTitle"]:
|
| 102 |
+
if key in obj and isinstance(obj[key], str):
|
| 103 |
+
cand_title = obj[key]
|
| 104 |
+
break
|
| 105 |
+
if target_label in str(cand_title):
|
| 106 |
+
hits.append(obj)
|
| 107 |
+
|
| 108 |
+
# 再帰探索
|
| 109 |
+
for v in obj.values():
|
| 110 |
+
hits.extend(find_article_blocks(v, target_label))
|
| 111 |
+
|
| 112 |
+
elif isinstance(obj, list):
|
| 113 |
+
for v in obj:
|
| 114 |
+
hits.extend(find_article_blocks(v, target_label))
|
| 115 |
+
|
| 116 |
+
return hits
|
| 117 |
+
|
| 118 |
+
def extract_article_text(article_block:dict)->str:
|
| 119 |
+
"""
|
| 120 |
+
条文塊から本文を抽出。段落・号などをざっくり連結。
|
| 121 |
+
v2のJSONは schema が揺れるので、汎用的に全要素からテキストを吸い出す。
|
| 122 |
+
"""
|
| 123 |
+
# まず見出し
|
| 124 |
+
title = ""
|
| 125 |
+
for k in ["articleTitle","articleNumber","ArticleTitle","ArticleNumber"]:
|
| 126 |
+
if k in article_block and isinstance(article_block[k], str):
|
| 127 |
+
title = article_block[k]
|
| 128 |
+
break
|
| 129 |
+
|
| 130 |
+
# 本文候補(paragraphs/items などを総なめ)
|
| 131 |
+
body = []
|
| 132 |
+
# 代表的な場所
|
| 133 |
+
for k in ["paragraphs","Paragraph","paragraph","items","Item"]:
|
| 134 |
+
if k in article_block:
|
| 135 |
+
body.append(flatten_text(article_block[k]))
|
| 136 |
+
# 念のため全体からも
|
| 137 |
+
body.append(flatten_text(article_block))
|
| 138 |
+
|
| 139 |
+
text = "\n".join([b for b in body if b]).strip()
|
| 140 |
+
# 見出し重複を軽く除去
|
| 141 |
+
text = re.sub(r"\n{2,}", "\n", text)
|
| 142 |
+
return (title + "\n\n" if title else "") + text
|
| 143 |
+
|
| 144 |
+
def run(q:str)->str:
|
| 145 |
try:
|
| 146 |
law, num = parse_query(q)
|
| 147 |
law_id = find_law_id(law)
|
| 148 |
+
data = fetch_law_json(law_id)
|
| 149 |
+
|
| 150 |
+
# 法令名
|
| 151 |
+
law_name = data.get("lawName") or data.get("LawName") or "不明な法令"
|
| 152 |
+
target = f"第{int_to_kanji(num)}条"
|
| 153 |
+
|
| 154 |
+
blocks = find_article_blocks(data, target)
|
| 155 |
+
if not blocks:
|
| 156 |
+
raise ValueError(f"{law_name} に {target} が見つからない。")
|
| 157 |
+
|
| 158 |
+
# 最初の一致を表示
|
| 159 |
+
text = extract_article_text(blocks[0]).strip()
|
| 160 |
+
if not text:
|
| 161 |
+
raise ValueError("本文の抽出に失敗。")
|
| 162 |
+
return f"{target}\n\n{text}"
|
| 163 |
+
|
| 164 |
except Exception as e:
|
| 165 |
return f"❌ エラー: {e}"
|
| 166 |
|
| 167 |
demo = gr.Interface(
|
| 168 |
+
fn=run,
|
| 169 |
+
inputs=gr.Textbox(label="例)民法3条 / 会社法10条 / 不動産登記法47条(「3上」でもOK)"),
|
| 170 |
+
outputs=gr.Textbox(label="結果", lines=18),
|
| 171 |
+
title="e-Gov 法令API v2|条文ピンポイント検索(最小構成)",
|
| 172 |
+
description="法令名+条番号を入れると該当条文を表示。JSON構造の違いにも(わりと)頑健。"
|
| 173 |
)
|
| 174 |
|
| 175 |
demo.launch()
|