elaws-parser 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,19 @@
1
+ from .hourei_apiv2 import (
2
+ get_lawid_from_lawtitle,
3
+ get_lawdata_from_law_id,
4
+ get_lawdata_from_lawname,
5
+ save_xml_string_to_file,
6
+ extract_sections_from_xml,
7
+ )
8
+ from .text_converter import convert_xml_to_text, LawXmlParser
9
+ from .yaml_converter import convert_xml_to_yaml, LawToYamlConverter
10
+
11
+ # LLM/LangGraph機能はオプショナル依存関係のため、インストールされていない場合は無視する
12
+ try:
13
+ from .law_extraction_v2 import (
14
+ LegalExtractionConfig,
15
+ create_legal_extraction_system,
16
+ YamlArticleExtractor,
17
+ )
18
+ except ImportError:
19
+ pass
@@ -0,0 +1,128 @@
1
+ """
2
+ eGovのAPI v2を利用して,法令を取得するコード
3
+ 取得した法令のxml構造を解析して,必要な情報を返す.
4
+ """
5
+
6
+ # TODO :: パーサーは,textのパーサーとyamlのパーサー
7
+
8
+ from __future__ import annotations
9
+
10
+ from functools import lru_cache
11
+ from typing import Dict, Literal
12
+ from xml.etree import ElementTree
13
+
14
+ import requests
15
+
16
+
17
+ @lru_cache
18
+ def get_lawid_from_lawtitle(
19
+ law_title: str, *, if_exact: bool = True
20
+ ) -> str | Dict[str, str]:
21
+ """APIから法令タイトルでヒットする法令IDを取得(完全一致のみ)"""
22
+ url = "https://laws.e-gov.go.jp/api/2/laws"
23
+ r = requests.get(url, params={"response_format": "xml", "law_title": law_title})
24
+ # XMLデータの解析
25
+ root = ElementTree.fromstring(r.content.decode(encoding="utf-8"))
26
+
27
+ laws_elem = root.find("laws")
28
+ if laws_elem is None:
29
+ print("Error: 'laws' element not found in response.")
30
+ return {}
31
+
32
+ counter = 0
33
+ law_dict = {} # 辞書{名称: 法令番号}の作成
34
+ for law in laws_elem.findall("law"): # loop over <law> elements
35
+ counter += 1
36
+
37
+ law_info = law.find("law_info")
38
+ revision_info = law.find("revision_info")
39
+
40
+ if law_info is None or revision_info is None:
41
+ continue # skip incomplete entries
42
+
43
+ law_id: str = law_info.findtext("law_id", default="(no id)")
44
+ law_num: str = law_info.findtext("law_num", default="(no number)")
45
+ lawtitle: str = revision_info.findtext("law_title", default="(no title)")
46
+
47
+ print(f"ID: {law_id}, Num: {law_num}, Title: {lawtitle}")
48
+ law_dict[lawtitle] = law_id
49
+ print(f"Number of laws: {counter}")
50
+ if if_exact:
51
+ return law_dict[law_title] # allow exact match
52
+ return law_dict # return all matches
53
+
54
+
55
+ def get_lawdata_from_law_id(law_id: str, output_type: Literal["xml", "list"]):
56
+ """法令IDから法令データを取得"""
57
+ url = f"https://laws.e-gov.go.jp/api/2/law_data/{law_id}"
58
+ r = requests.get(url, params={"response_format": "xml"})
59
+ if r.status_code != 200:
60
+ print(f"Error fetching law data for ID {law_id}: {r.status_code}")
61
+ return None
62
+ if output_type == "xml":
63
+ return r.content.decode(encoding="utf-8")
64
+
65
+ if output_type == "list":
66
+ # XMLデータの解析
67
+ root = ElementTree.fromstring(r.content.decode(encoding="utf-8"))
68
+ contents = [e.text.strip() for e in root.iter() if e.text]
69
+ return [t for t in contents if t]
70
+ raise ValueError(f"Supported output type is xml or list. Got {output_type}")
71
+
72
+
73
+ def get_lawdata_from_lawname(law_name: str) -> str:
74
+ """法令名から法令データを取得(完全一致のみ)"""
75
+ law_id: str = get_lawid_from_lawtitle(law_name, if_exact=True)
76
+ law_text: str = get_lawdata_from_law_id(law_id, "xml")
77
+ return law_text
78
+
79
+
80
+ def save_xml_string_to_file(xml_string: str, filename: str):
81
+ """save xml string to a file"""
82
+ with open(filename, "w", encoding="utf-8") as f:
83
+ f.write(xml_string)
84
+
85
+
86
+ def extract_sections_from_xml(xml_string: str) -> Dict[str, str | None | list[str]]:
87
+ """TOC, MainProvision,SupplProvisionの3つを取得"""
88
+ root = ElementTree.fromstring(xml_string)
89
+
90
+ # law_infoタグを取得
91
+ law_full_text = root.find("law_full_text")
92
+ if law_full_text is None:
93
+ raise ValueError("law_full_textタグが見つかりません")
94
+
95
+ # <Law> の中にある <LawBody> を探す
96
+ law = law_full_text.find("Law")
97
+ if law is None:
98
+ raise ValueError("<Law> タグが <law_full_text> 内に見つかりません")
99
+
100
+ law_body = law.find("LawBody")
101
+ if law_body is None:
102
+ raise ValueError("<LawBody> タグが <Law> 内に見つかりません")
103
+
104
+ # 対象の3つのタグを取得
105
+ toc = law_body.find("TOC")
106
+ main_prov = law_body.find("MainProvision")
107
+ suppl_provs = law_body.findall("SupplProvision")
108
+
109
+ return {
110
+ "TOC": (
111
+ ElementTree.tostring(toc, encoding="unicode") if toc is not None else None
112
+ ),
113
+ "MainProvision": (
114
+ ElementTree.tostring(main_prov, encoding="unicode")
115
+ if main_prov is not None
116
+ else None
117
+ ),
118
+ # "SupplProvision": (
119
+ # ElementTree.tostring(suppl_prov, encoding="unicode")
120
+ # if suppl_prov is not None
121
+ # else None
122
+ # ),
123
+ "SupplProvision": (
124
+ [ElementTree.tostring(s, encoding="unicode") for s in suppl_provs]
125
+ if suppl_provs
126
+ else None
127
+ ),
128
+ }
@@ -0,0 +1,484 @@
1
+ import logging
2
+ from abc import ABC, abstractmethod
3
+ from dataclasses import dataclass, field
4
+ from enum import Enum
5
+ from pathlib import Path
6
+ from typing import Any, Dict, List, Literal, Optional, TypedDict
7
+
8
+ import yaml
9
+ from langchain_core.language_models import BaseLLM
10
+ from langchain_core.messages import BaseMessage, SystemMessage
11
+ from langchain_core.prompts import PromptTemplate
12
+ from langchain_openai import ChatOpenAI
13
+ from langgraph.graph import END, StateGraph # CompiledGraph
14
+ from pydantic import BaseModel, Field
15
+
16
+ # ロギング設定
17
+ logging.basicConfig(level=logging.INFO)
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class ProcessingStage(Enum):
22
+ """4つの処理段階の定義.エラー処理等で利用"""
23
+
24
+ LAW_EXTRACTION = "law_extraction"
25
+ REGULATION_EXTRACTION = "regulation_extraction"
26
+ SUMMARY_GENERATION = "summary_generation"
27
+ COMPLETED = "completed"
28
+
29
+
30
+ @dataclass
31
+ class ExtractionResult:
32
+ """抽出結果のデータクラス"""
33
+
34
+ content: str
35
+ metadata: Dict[str, Any] = field(default_factory=dict)
36
+ token_count: Optional[int] = None
37
+ processing_time: Optional[float] = None
38
+
39
+
40
+ @dataclass
41
+ class LegalDocument:
42
+ """法令文書のデータクラス(YAML対応版)"""
43
+
44
+ name: str
45
+ content: str # 法令全文
46
+ document_type: Literal["law", "regulation"] # 法令 or 施行規則
47
+ yaml_data: Optional[Dict[str, Any]] = None # YAML構造データを追加
48
+ articles: Optional[List[str]] = None
49
+ metadata: Dict[str, Any] = field(default_factory=dict)
50
+
51
+ def __post_init__(self):
52
+ """文書の基本検証"""
53
+ if not self.name or not self.content:
54
+ raise ValueError("文書名と内容は必須です")
55
+
56
+ # YAML構造データが提供されていない場合の警告
57
+ if self.yaml_data is None:
58
+ logger.warning(
59
+ f"{self.name}: YAML構造データが提供されていません。新しい抽出機能は使用できません。"
60
+ )
61
+
62
+
63
+ class GraphState(TypedDict):
64
+ """グラフの状態管理用TypedDict"""
65
+
66
+ law_document: LegalDocument # 法令文書
67
+ regulation_document: LegalDocument # 施行規則文書
68
+ target_articles: List[str] # 抽出対象の条文リスト
69
+ extracted_law_article_numbers: Optional[List[int]] # 抽出された法令の条文
70
+ extracted_law_content: Optional[str] # 抽出された法令内容
71
+ extracted_regulation_content: Optional[str] # 抽出された施行規則内容
72
+ extracted_regulation_article_numbers: Optional[
73
+ List[int]
74
+ ] # 抽出された施行規則の条項一覧
75
+ final_summary: Optional[str] # 最終的な要点
76
+ current_stage: ProcessingStage # 現在の処理段階
77
+ error_message: Optional[str] # エラーメッセージ
78
+ metadata: Dict[str, Any] # 処理メタデータ
79
+ application_item: Optional[str] # 申請項目
80
+
81
+
82
+ def flatten_state(state: GraphState) -> dict[str, str]:
83
+ """stateからプロンプト展開用のcontextを自動生成"""
84
+ context = {}
85
+ for key, value in state.items():
86
+ if hasattr(value, "name") and hasattr(value, "content"):
87
+ context[f"{key}_name"] = value.name
88
+ context[f"{key}_text"] = value.content
89
+ elif hasattr(value, "name"):
90
+ context[f"{key}_name"] = value.name
91
+ elif isinstance(value, (str, int, float)):
92
+ context[key] = str(value)
93
+ # 必要なら他の型にも対応
94
+ return context
95
+
96
+
97
+ class PromptManager:
98
+ """プロンプト管理クラス
99
+ TODO :: prompts_dirをここで管理して良いかどうか.
100
+ 理想的には,load_promptでまとめてdir/fileを指定したい.
101
+ そのためには,各部でload_promptを呼び出している部分のファイル名をハードコードから外から与える形にする必要がある.
102
+ """
103
+
104
+ def __init__(self, prompts_dir: Path = Path("prompts")):
105
+ self.prompts_dir = prompts_dir
106
+ self._prompts_cache: Dict[str, PromptTemplate] = {}
107
+
108
+ def load_prompt(self, prompt_name: str) -> PromptTemplate:
109
+ """プロンプトテンプレートの読み込み(キャッシュ機能付き)"""
110
+ if prompt_name in self._prompts_cache:
111
+ return self._prompts_cache[prompt_name]
112
+
113
+ prompt_path = self.prompts_dir / f"{prompt_name}.yaml"
114
+ if not prompt_path.exists():
115
+ raise FileNotFoundError(
116
+ f"プロンプトファイルが見つかりません: {prompt_path}"
117
+ )
118
+
119
+ with open(prompt_path, "r", encoding="utf-8") as f:
120
+ prompt_data = yaml.safe_load(f)
121
+
122
+ template = PromptTemplate(
123
+ input_variables=prompt_data.get("input_variables", []),
124
+ template=prompt_data["template"],
125
+ )
126
+
127
+ self._prompts_cache[prompt_name] = template
128
+ return template
129
+
130
+ def render_prompt(self, name: str, context: dict) -> str:
131
+ """プロンプトテンプレートから必要な引数を自動で取得"""
132
+ prompt_template = self.load_prompt(name)
133
+
134
+ # 必要なキーだけ抽出して format に渡す
135
+ subset = {
136
+ k: context[k] for k in prompt_template.input_variables if k in context
137
+ }
138
+ print(f"debug subset = {subset}")
139
+ return prompt_template.template.format(**subset)
140
+
141
+
142
+ class ViewPointResult(BaseModel):
143
+ """判断軸のstructured output用Pydanticモデル"""
144
+
145
+ viewpoint: str = Field(description="官庁への申請が必要な場合の観点")
146
+ annotation: str = Field(description="注釈")
147
+
148
+
149
+ class BaseExtractor(ABC):
150
+ """抽出処理の基底クラス"""
151
+
152
+ def __init__(self, llm: BaseLLM, prompt_manager: PromptManager, prompt_name: str):
153
+ self.llm = llm
154
+ self.prompt_manager = prompt_manager
155
+ self.prompt_name = prompt_name
156
+
157
+ @abstractmethod
158
+ def extract(self, state: GraphState) -> ExtractionResult:
159
+ """抽出処理の実行.ここで_create_messagesと_invoke_llmを呼び出す"""
160
+ pass
161
+
162
+ def _create_messages(self, formatted_prompt: str) -> List[BaseMessage]:
163
+ """メッセージリストの生成"""
164
+ return [SystemMessage(content=formatted_prompt)]
165
+
166
+ def _invoke_llm(self, messages: List[BaseMessage]) -> str:
167
+ """LLMの呼び出しと例外処理"""
168
+ try:
169
+ response = self.llm.invoke(messages)
170
+ return response.content if hasattr(response, "content") else str(response)
171
+ except Exception as e:
172
+ logger.error(f"LLM呼び出しエラー: {e}")
173
+ raise
174
+
175
+
176
+ class LawExtractor(BaseExtractor):
177
+ """法令本体からの関連条文抽出"""
178
+
179
+ def extract(self, state: GraphState) -> ExtractionResult:
180
+ """法令から関連条文を抽出"""
181
+ logger.info("法令からの関連条文抽出を開始")
182
+
183
+ # FIXME :: プロンプトのより良い読み込み方法を考える.
184
+ prompt_template = self.prompt_manager.load_prompt(self.prompt_name)
185
+ formatted_prompt = prompt_template.format(
186
+ law_name=state["law_document"].name,
187
+ law_article=", ".join(state["target_articles"]),
188
+ law_text=state["law_document"].content,
189
+ )
190
+
191
+ messages = self._create_messages(formatted_prompt)
192
+ extracted_content = self._invoke_llm(messages)
193
+
194
+ return ExtractionResult(
195
+ content=extracted_content,
196
+ metadata={
197
+ "stage": "law_extraction",
198
+ "source_document": state["law_document"].name,
199
+ "target_articles": state["target_articles"],
200
+ },
201
+ )
202
+
203
+
204
+ class RegulationExtractor(BaseExtractor):
205
+ """施行規則からの関連条文抽出"""
206
+
207
+ def extract(self, state: GraphState) -> ExtractionResult:
208
+ """施行規則から関連条文を抽出"""
209
+ logger.info("施行規則からの関連条文抽出を開始")
210
+
211
+ # FIXME :: プロンプトのより良い読み込み方法を考える.
212
+ prompt_template = self.prompt_manager.load_prompt(self.prompt_name)
213
+ formatted_prompt = prompt_template.format(
214
+ law_name=state["law_document"].name,
215
+ extracted_law_content=state["extracted_law_content"],
216
+ regulation_text=state["regulation_document"].content,
217
+ )
218
+
219
+ messages = self._create_messages(formatted_prompt)
220
+ extracted_content = self._invoke_llm(messages)
221
+
222
+ return ExtractionResult(
223
+ content=extracted_content,
224
+ metadata={
225
+ "stage": "regulation_extraction",
226
+ "source_document": state["regulation_document"].name,
227
+ "law_reference": state["law_document"].name,
228
+ },
229
+ )
230
+
231
+
232
+ class ViewpointGenerator(BaseExtractor):
233
+ """最終判断軸生成"""
234
+
235
+ def extract(self, state: GraphState) -> ExtractionResult:
236
+ """抽出した法令(state[extracted_law_content],state[extracted_regulation_content])を利用して,最終的な要点を生成"""
237
+ logger.info("最終的な判断軸生成を開始")
238
+
239
+ # プロンプトの読み込み
240
+ base_context = flatten_state(state)
241
+ special_context = {
242
+ "law_name": state["law_document"].name,
243
+ "law_article": ", ".join(state["target_articles"]),
244
+ "law_text": state["extracted_law_content"],
245
+ "enforcement_regulations": state["extracted_regulation_content"],
246
+ }
247
+
248
+ formatted_prompt = self.prompt_manager.render_prompt(
249
+ self.prompt_name,
250
+ context={**base_context, **special_context},
251
+ )
252
+
253
+ messages = self._create_messages(formatted_prompt)
254
+ structured_llm = self.llm.with_structured_output(ViewPointResult)
255
+ summary = structured_llm.invoke(messages)
256
+
257
+ return ExtractionResult(
258
+ content=summary,
259
+ metadata={
260
+ "stage": "summary_generation",
261
+ "law_document": state["law_document"].name,
262
+ "regulation_document": state["regulation_document"].name,
263
+ },
264
+ )
265
+
266
+
267
+ class GraphBuilder:
268
+ """法令要点抽出のグラフビルダー"""
269
+
270
+ # 各LLM呼び出しのプロンプト名
271
+ DEFAULT_PROMPT_NAMES = {
272
+ "extract_law": "extract_laws_v001",
273
+ "extract_regulation": "extract_regulation_v001",
274
+ "generate_summary": "v003",
275
+ }
276
+
277
+ def __init__(
278
+ self,
279
+ llm: BaseLLM,
280
+ prompts_dir: Path = Path("prompts"),
281
+ prompt_names: Optional[Dict[str, str]] = None,
282
+ ):
283
+ self.llm = llm
284
+ self.prompt_manager = PromptManager(prompts_dir)
285
+
286
+ # デフォルトとユーザ指定をマージ(ユーザ指定が優先)
287
+ self.prompt_names = {**self.DEFAULT_PROMPT_NAMES, **(prompt_names or {})}
288
+
289
+ # 各抽出器の初期化
290
+ self.law_extractor = LawExtractor(
291
+ llm, self.prompt_manager, self.prompt_names["extract_law"]
292
+ )
293
+ self.regulation_extractor = RegulationExtractor(
294
+ llm, self.prompt_manager, self.prompt_names["extract_regulation"]
295
+ )
296
+ self.summary_generator = ViewpointGenerator(
297
+ llm, self.prompt_manager, self.prompt_names["generate_summary"]
298
+ )
299
+
300
+ # グラフの構築
301
+ self.graph = self._build_graph()
302
+
303
+ def _build_graph(self): # TODO:: CompiledGraph型の戻り値を指定
304
+ """LangGraphの構築"""
305
+ workflow = StateGraph(GraphState)
306
+
307
+ # ノードの追加
308
+ workflow.add_node("extract_law", self._extract_law_node)
309
+ workflow.add_node("extract_regulation", self._extract_regulation_node)
310
+ workflow.add_node("generate_summary", self._generate_summary_node)
311
+ workflow.add_node("handle_error", self._handle_error_node)
312
+
313
+ # エッジの設定
314
+ workflow.set_entry_point("extract_law")
315
+
316
+ workflow.add_conditional_edges(
317
+ "extract_law",
318
+ self._should_continue_to_regulation,
319
+ {"continue": "extract_regulation", "error": "handle_error"},
320
+ )
321
+
322
+ workflow.add_conditional_edges(
323
+ "extract_regulation",
324
+ self._should_continue_to_summary,
325
+ {"continue": "generate_summary", "error": "handle_error"},
326
+ )
327
+
328
+ workflow.add_edge("generate_summary", END)
329
+ workflow.add_edge("handle_error", END)
330
+
331
+ return workflow.compile()
332
+
333
+ def _extract_law_node(self, state: GraphState) -> GraphState:
334
+ """法令抽出ノード"""
335
+ try:
336
+ result = self.law_extractor.extract(state)
337
+ state["extracted_law_content"] = result.content
338
+ state["current_stage"] = ProcessingStage.LAW_EXTRACTION
339
+ state["metadata"].update(result.metadata)
340
+ logger.info("法令抽出が完了しました")
341
+ except Exception as e:
342
+ state["error_message"] = f"法令抽出エラー: {str(e)}"
343
+ logger.error(state["error_message"])
344
+
345
+ return state
346
+
347
+ def _extract_regulation_node(self, state: GraphState) -> GraphState:
348
+ """施行規則抽出ノード"""
349
+ try:
350
+ result = self.regulation_extractor.extract(state)
351
+ state["extracted_regulation_content"] = result.content
352
+ state["current_stage"] = ProcessingStage.REGULATION_EXTRACTION
353
+ state["metadata"].update(result.metadata)
354
+ logger.info("施行規則抽出が完了しました")
355
+ except Exception as e:
356
+ state["error_message"] = f"施行規則抽出エラー: {str(e)}"
357
+ logger.error(state["error_message"])
358
+
359
+ return state
360
+
361
+ def _generate_summary_node(self, state: GraphState) -> GraphState:
362
+ """要点生成ノード"""
363
+ try:
364
+ result = self.summary_generator.extract(state)
365
+ state["final_summary"] = result.content
366
+ state["current_stage"] = ProcessingStage.COMPLETED
367
+ state["metadata"].update(result.metadata)
368
+ logger.info("要点生成が完了しました")
369
+ except Exception as e:
370
+ state["error_message"] = f"要点生成エラー: {str(e)}"
371
+ logger.error(state["error_message"])
372
+
373
+ return state
374
+
375
+ def _handle_error_node(self, state: GraphState) -> GraphState:
376
+ """エラーハンドリングノード"""
377
+ logger.error(
378
+ f"処理中にエラーが発生しました: {state.get('error_message', '不明なエラー')}"
379
+ )
380
+ state["current_stage"] = ProcessingStage.COMPLETED
381
+ return state
382
+
383
+ def _should_continue_to_regulation(self, state: GraphState) -> str:
384
+ """施行規則抽出への継続判定"""
385
+ return "error" if state.get("error_message") else "continue"
386
+
387
+ def _should_continue_to_summary(self, state: GraphState) -> str:
388
+ """要点生成への継続判定"""
389
+ return "error" if state.get("error_message") else "continue"
390
+
391
+ def process(
392
+ self,
393
+ law_document: LegalDocument,
394
+ regulation_document: LegalDocument,
395
+ target_articles: List[str],
396
+ ) -> GraphState:
397
+ """処理の実行"""
398
+ initial_state = GraphState(
399
+ law_document=law_document,
400
+ regulation_document=regulation_document,
401
+ target_articles=target_articles,
402
+ extracted_law_content=None,
403
+ extracted_regulation_content=None,
404
+ final_summary=None,
405
+ current_stage=ProcessingStage.LAW_EXTRACTION,
406
+ error_message=None,
407
+ metadata={},
408
+ )
409
+
410
+ logger.info("法令判断軸抽出処理を開始します")
411
+ result = self.graph.invoke(initial_state)
412
+ logger.info(f"処理が完了しました。ステージ: {result['current_stage']}")
413
+
414
+ return result
415
+
416
+
417
+ class LegalExtractionConfig:
418
+ """設定管理クラス"""
419
+
420
+ def __init__(
421
+ self,
422
+ llm,
423
+ prompts_dir: str = "prompts",
424
+ prompt_names: Optional[Dict[str, str]] = None,
425
+ ):
426
+ self.llm = llm
427
+ self.prompts_dir = Path(prompts_dir)
428
+ self.prompt_names = prompt_names
429
+
430
+
431
+ def create_legal_extraction_system(config: LegalExtractionConfig) -> GraphBuilder:
432
+ """法令要点抽出システムのファクトリー関数"""
433
+ return GraphBuilder(
434
+ llm=config.llm, prompts_dir=config.prompts_dir, prompt_names=config.prompt_names
435
+ )
436
+
437
+
438
+ # 使用例
439
+ def example_usage():
440
+ """使用例の実装"""
441
+
442
+ # 設定の初期化
443
+ config = LegalExtractionConfig(
444
+ model_name="gpt-4o-mini",
445
+ temperature=0.1,
446
+ prompts_dir="prompts",
447
+ )
448
+
449
+ # システムの初期化
450
+ extraction_system = create_legal_extraction_system(config)
451
+
452
+ # サンプルデータ
453
+ law_document = LegalDocument(
454
+ name="土壌汚染対策法", content="(ここに法令本文が入る)", document_type="law"
455
+ )
456
+
457
+ regulation_document = LegalDocument(
458
+ name="土壌汚染対策法施行規則",
459
+ content="(ここに施行規則本文が入る)",
460
+ document_type="regulation",
461
+ )
462
+
463
+ target_articles = ["第3条", "第4条"]
464
+
465
+ # 処理の実行
466
+ try:
467
+ result = extraction_system.process(
468
+ law_document=law_document,
469
+ regulation_document=regulation_document,
470
+ target_articles=target_articles,
471
+ )
472
+
473
+ if result["error_message"]:
474
+ print(f"エラーが発生しました: {result['error_message']}")
475
+ else:
476
+ print("=== 最終要点 ===")
477
+ print(result["final_summary"])
478
+
479
+ print("\n=== メタデータ ===")
480
+ for key, value in result["metadata"].items():
481
+ print(f"{key}: {value}")
482
+
483
+ except Exception as e:
484
+ logger.error(f"処理中に予期しないエラーが発生しました: {e}")