nicebeam commited on
Commit
ecfad21
·
verified ·
1 Parent(s): e9f6c95

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +118 -54
app.py CHANGED
@@ -1,10 +1,13 @@
1
  import re
 
2
  import requests
3
  import gradio as gr
4
 
5
- BASE_URL = "https://laws.e-gov.go.jp/api/2/laws"
 
 
6
 
7
- # よく使う法令はIDを直持ち(民法ほか数件)
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
- # 「民法3上」みたいな誤入力も「条」に補正
36
- q = re.sub(r"(?<=\d)上$", "条", q)
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
- law = m.group("law")
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
- # キーワード検索で候補取得(v2の一覧API)
52
- params = {"keyword": law_name, "num": 20}
53
- r = requests.get(BASE_URL, params=params, timeout=15)
54
  r.raise_for_status()
55
- data = r.json()
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 get_article_text(law_id:str, article_no:int)->str:
66
- # v2の法令取得(lawId指定)。articles配列から該当条を拾う前提
67
- r = requests.get(BASE_URL, params={"lawId": law_id}, timeout=30)
68
- r.raise_for_status()
69
- j = r.json()
70
-
71
- law_name = j.get("lawName", "不明な法令")
72
- articles = j.get("articles", [])
73
-
74
- target_label = f"第{int_to_kanji(article_no)}条"
75
- # articles: [{articleTitle, articleNumber, paragraphs:[{paragraphSentence:..}, ...]}, ...]
76
- for a in articles:
77
- # API仕様により articleNumber / articleTitle のどちらかに「第◯条」が入るケースを吸収
78
- number = a.get("articleNumber") or a.get("articleTitle") or ""
79
- if target_label in str(number):
80
- paras = a.get("paragraphs", [])
81
- # 段落を結合
82
- body = []
83
- for p in paras:
84
- # 号・項などの入れ子にも軽く対応
85
- s = p.get("paragraphSentence") or ""
86
- items = p.get("items") or []
87
- body.append(s)
88
- for it in items:
89
- isent = it.get("itemSentence") or ""
90
- body.append(isent)
91
- content = "\n".join([x for x in body if x])
92
- return f"{target_label}\n\n{content.strip() or '本文が見つかりませんでした。'}"
93
- raise ValueError(f"{law_name} に {target_label} が見つからない。")
94
-
95
- def query_runner(q:str)->str:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  try:
97
  law, num = parse_query(q)
98
  law_id = find_law_id(law)
99
- return get_article_text(law_id, num)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  except Exception as e:
101
  return f"❌ エラー: {e}"
102
 
103
  demo = gr.Interface(
104
- fn=query_runner,
105
- inputs=gr.Textbox(label="例)民法3条 / 会社法10条 / 不動産登記法47条"),
106
- outputs=gr.Textbox(label="結果", lines=16),
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()