Legal-Vectorless-RAG-HF / src /tree_builder.py
ayanshuDS
Deploy to HF without binaries
685cc60
Raw
History Blame Contribute Delete
6.56 kB
import os
import json
import hashlib
import pandas as pd
def process_xrefs(xref_str, current_act):
"""
Process cross_references JSON string from Phase 1.
Splits into internal_refs (e.g. 'S64') and cross_act_refs (e.g. {'act': 'BNSS', 'section': '173'}).
"""
internal = []
cross = []
if not xref_str:
return internal, cross
try:
refs = json.loads(xref_str)
except Exception:
refs = []
for ref in refs:
if ref.startswith(f"{current_act}_"):
# Internal reference, strip act prefix (e.g. 'BNS_S85' -> 'S85')
stripped = ref[len(current_act)+1:]
internal.append(stripped)
else:
# Cross-act reference (e.g. 'BNSS_S173' -> {'act': 'BNSS', 'section': '173'})
parts = ref.split("_", 1)
if len(parts) == 2:
act, sec = parts
if sec.startswith("S"):
sec = sec[1:]
cross.append({"act": act, "section": sec})
return internal, cross
def build_unsummarized_trees(output_dir="output"):
"""
Loads Parquet files and constructs the unsummarized tree structures for BNS, BNSS, and BSA.
Returns a dict mapping act_code -> root_node_dict.
"""
toc_path = os.path.join(output_dir, "toc_df.parquet")
line_path = os.path.join(output_dir, "line_df.parquet")
schedule_path = os.path.join(output_dir, "schedule_df.parquet")
if not os.path.exists(toc_path) or not os.path.exists(line_path):
raise FileNotFoundError("Required Parquet files are missing in output directory.")
toc_df = pd.read_parquet(toc_path)
line_df = pd.read_parquet(line_path)
schedule_df = pd.read_parquet(schedule_path) if os.path.exists(schedule_path) else pd.DataFrame()
# Concatenate text per section_id in line_df
print("Concatenating section line contents...")
section_texts = line_df.groupby("section_id")["text"].apply(lambda lines: " ".join(lines)).to_dict()
acts = ["BNS", "BNSS", "BSA"]
if "SOP" in toc_df["act_code"].values:
acts.append("SOP")
trees = {}
for act in acts:
print(f"Building skeleton tree for {act}...")
act_toc = toc_df[toc_df["act_code"] == act].copy()
# Instantiate all nodes as dicts
node_map = {}
root_node = None
for _, row in act_toc.iterrows():
node_id = row["section_id"]
level = int(row["level"])
node_type = row["node_type"]
title = row["title"]
# Content population
content = None
if node_type in ["section", "sop_procedure", "sop_form", "sop_reference", "sop_table"]:
content = section_texts.get(node_id, "")
# Cross references
internal_refs, cross_act_refs = process_xrefs(row["cross_references"], act)
token_est = len(content) // 4 if content else 0
node = {
"node_id": node_id,
"level": level,
"node_type": node_type,
"title": title,
"summary": None,
"content": content,
"children": [],
"metadata": {
"act_code": act,
"page_range": [int(row["start_page"]), int(row["end_page"])],
"internal_refs": internal_refs,
"cross_act_refs": cross_act_refs,
"token_estimate": token_est,
"stable_hash": row["stable_hash"]
}
}
node_map[node_id] = node
if level == 0 and node_type == "root":
root_node = node
# Build parent-child relationships
for _, row in act_toc.iterrows():
node_id = row["section_id"]
parent_id = row["parent_id"]
if pd.isna(parent_id) or parent_id is None:
continue
if parent_id in node_map and node_id in node_map:
node_map[parent_id]["children"].append(node_map[node_id])
else:
print(f"Warning: parent_id {parent_id} or node_id {node_id} not found in node_map.")
# Special schedule handling for BNSS
if act == "BNSS" and not schedule_df.empty:
schedule_node_id = "BNSS_SCH1"
if schedule_node_id in node_map:
print("Appending First Schedule table rows as leaf nodes...")
for idx, row in schedule_df.iterrows():
row_content = (
f"Section: {row['section']}\n"
f"Offence: {row['offence']}\n"
f"Punishment: {row['punishment']}\n"
f"Cognizable: {row['cognizable']}\n"
f"Bailable: {row['bailable']}\n"
f"Court: {row['court']}"
)
row_id = f"BNSS_SCH1_R{idx}"
# Try to parse section number for internal reference
section_num = str(row['section']).strip()
internal_refs = []
if section_num:
internal_refs.append(f"S{section_num}")
row_node = {
"node_id": row_id,
"level": 2,
"node_type": "schedule_row",
"title": f"Schedule Row: Offence under Section {row['section']}",
"summary": None,
"content": row_content,
"children": [],
"metadata": {
"act_code": "BNSS",
"page_range": [int(row["page_no"]), int(row["page_no"])],
"internal_refs": internal_refs,
"cross_act_refs": [],
"token_estimate": len(row_content) // 4,
"stable_hash": hashlib.sha1(row_id.encode()).hexdigest()
}
}
node_map[schedule_node_id]["children"].append(row_node)
if root_node:
trees[act] = root_node
else:
raise ValueError(f"Root node for {act} was not found in TOC.")
return trees