legalgpt-api / backend /layer6_knowledge_graph.py
Lvvbaaal's picture
Deploy SaulGPT backend with full ChromaDB, Supabase, and Gradio API server
62be02c verified
Raw History Blame Contribute Delete
26.1 kB
"""
SAULSGPT — LAYER 6: LEGAL KNOWLEDGE GRAPH
==========================================
Uses NetworkX to map institutional relationships across Indian law.
Acts as a high-speed (<1ms) post-retrieval context expander and reasoning anchor.
What it maps:
→ Act level replacements (IPC → BNS, CrPC → BNSS, IEA → BSA 2023)
→ Constitutional Overrides & Fundamental Rights (Articles 14, 19, 21, 22, 32, 226, 300A)
→ Criminal offences, punishments, and definitions (BNS / IPC)
→ Criminal procedure pipelines (Zero FIR → SP Escalation → S.156(3) Magistrate → S.482 Quashing)
→ Civil Procedure & Injunctions (Order 39, Order 7 Rule 11, Order 37 CPC)
→ Family & Matrimonial Law (HMA 13/24/25, HSA 6/8/30 Coparcenary, PWDVA 2005)
→ Commercial, Cheque Bounce & Arbitration (NI Act 138, S.27 Contract Act, Arb Act 1996)
→ Struck Down & Landmark Supreme Court Precedents (Shreya Singhal, Joseph Shine, Navtej Johar, Puttaswamy)
"""
import networkx as nx
from typing import List, Dict
# Canonical Act Aliases
ACT_ALIASES = {
"ipc": "IPC", "indian penal code": "IPC", "indian penal code, 1860": "IPC", "ipc_from_db": "IPC",
"crpc": "CrPC", "code of criminal procedure": "CrPC", "code of criminal procedure, 1973": "CrPC", "crpc_from_db": "CrPC",
"iea": "IEA", "indian evidence act": "IEA", "indian evidence act, 1872": "IEA", "iea_from_db": "IEA",
"bns": "BNS", "bns 2023": "BNS", "bharatiya nyaya sanhita": "BNS", "bharatiya nyaya sanhita 2023": "BNS",
"bnss": "BNSS", "bnss 2023": "BNSS", "bharatiya nagarik suraksha sanhita": "BNSS", "bnss_from_db": "BNSS",
"bsa": "BSA", "bsa 2023": "BSA", "bharatiya sakshya adhiniyam": "BSA", "bsa_from_db": "BSA",
"cpc": "CPC", "code of civil procedure": "CPC", "code of civil procedure, 1908": "CPC", "cpc_from_db": "CPC",
"constitution": "Constitution", "constitution of india": "Constitution", "the constitution of india": "Constitution",
"hma": "HMA", "hindu marriage act": "HMA", "hindu marriage act, 1955": "HMA", "hma_from_db": "HMA",
"hsa": "HSA", "hindu succession act": "HSA", "hindu succession act, 1956": "HSA",
"nia": "NIA", "negotiable instruments act": "NIA", "negotiable instruments act, 1881": "NIA", "nia_from_db": "NIA",
"ida": "IDA", "industrial disputes act": "IDA", "ida_from_db": "IDA",
"mva": "MVA", "motor vehicles act": "MVA", "motor vehicles act, 1988": "MVA", "mva_from_db": "MVA",
"pwdva": "PWDVA", "domestic violence act": "PWDVA", "protection of women from domestic violence act": "PWDVA"
}
def normalize_act_name(raw_name: str) -> str:
return ACT_ALIASES.get(raw_name.lower().strip(), raw_name)
class LegalKnowledgeGraph:
"""
Directed Knowledge Graph of Indian Law Relationships.
Built once at startup; queried in sub-millisecond time.
"""
def __init__(self):
print("[Graph] Initializing Institutional Legal Knowledge Graph...")
self.G = nx.DiGraph()
self._build_graph()
print(f"[Graph] Institutional Graph built: "
f"{self.G.number_of_nodes()} nodes, "
f"{self.G.number_of_edges()} edges\n")
def _build_graph(self):
"""Builds the comprehensive Indian legal relationship graph."""
# ── 1. MASTER ACT REPLACEMENTS (July 1, 2024) ──
self.G.add_edge("IPC", "BNS", relation="replaced_by", date="2024-07-01")
self.G.add_edge("CrPC", "BNSS", relation="replaced_by", date="2024-07-01")
self.G.add_edge("IEA", "BSA", relation="replaced_by", date="2024-07-01")
self.G.add_edge("Consumer_Protection_Act_1986", "Consumer_Protection_Act_2019", relation="replaced_by")
self.G.add_edge("Companies_Act_1956", "Companies_Act_2013", relation="replaced_by")
self.G.add_edge("Arbitration_Act_1940", "Arbitration_and_Conciliation_Act_1996", relation="replaced_by")
self.G.add_edge("FERA_1973", "FEMA_1999", relation="replaced_by")
self.G.add_edge("MRTP_Act_1969", "Competition_Act_2002", relation="replaced_by")
# ── 2. CONSTITUTIONAL HIERARCHY & WRIT REMEDIES ──
self.G.add_edge("Constitution", "IPC", relation="overrides")
self.G.add_edge("Constitution", "CrPC", relation="overrides")
self.G.add_edge("Constitution", "BNS", relation="overrides")
self.G.add_edge("Constitution", "BNSS", relation="overrides")
self.G.add_edge("Constitution", "CPC", relation="overrides")
self.G.add_edge("Article_14", "Equal_Protection", relation="guarantees")
self.G.add_edge("Article_19_1_a", "Free_Speech", relation="guarantees")
self.G.add_edge("Free_Speech", "Article_19_2", relation="subject_to_reasonable_restrictions")
self.G.add_edge("Article_20_1", "No_Ex_Post_Facto_Law", relation="guarantees")
self.G.add_edge("Article_20_2", "No_Double_Jeopardy", relation="guarantees")
self.G.add_edge("Article_20_3", "Right_Against_Self_Incrimination", relation="guarantees")
self.G.add_edge("Article_21", "Right_to_Life_and_Liberty", relation="guarantees")
self.G.add_edge("Article_21", "Right_to_Fair_Trial", relation="encompasses")
self.G.add_edge("Article_21", "Right_to_Privacy", relation="encompasses")
self.G.add_edge("Article_22", "Arrest_Safeguards", relation="guarantees")
self.G.add_edge("Article_32", "Supreme_Court_Writs", relation="empowers")
self.G.add_edge("Article_226", "High_Court_Writs", relation="empowers")
self.G.add_edge("Article_300A", "Right_to_Property", relation="protects_from_arbitrary_deprivation")
# ── 3. CRIMINAL LAW (BNS 2023 / IPC 1860) ──
# Murder & Homicide
self.G.add_edge("IPC_302", "BNS_103", relation="replaced_by")
self.G.add_edge("IPC_302", "IPC_300", relation="requires_definition_from")
self.G.add_edge("IPC_302", "IPC_299", relation="distinguished_from")
self.G.add_edge("IPC_302", "IPC_304", relation="lesser_offence")
self.G.add_edge("BNS_103", "BNS_101", relation="requires_definition_from")
# Theft, Extortion, Robbery
self.G.add_edge("IPC_378", "BNS_303", relation="replaced_by")
self.G.add_edge("IPC_379", "BNS_303_2", relation="replaced_by")
self.G.add_edge("IPC_383", "BNS_308", relation="replaced_by")
self.G.add_edge("IPC_384", "BNS_308_2", relation="replaced_by")
self.G.add_edge("IPC_390", "BNS_309", relation="replaced_by")
self.G.add_edge("IPC_392", "BNS_309_4", relation="replaced_by")
# Cheating, Forgery, Fraud
self.G.add_edge("IPC_415", "IPC_420", relation="defines")
self.G.add_edge("IPC_420", "BNS_318", relation="replaced_by")
self.G.add_edge("IPC_405", "IPC_406", relation="defines_criminal_breach_of_trust")
self.G.add_edge("IPC_406", "BNS_316", relation="replaced_by")
self.G.add_edge("IPC_463", "IPC_465", relation="defines_forgery")
self.G.add_edge("IPC_465", "BNS_336", relation="replaced_by")
self.G.add_edge("IPC_468", "BNS_338", relation="replaced_by")
self.G.add_edge("IPC_471", "BNS_340", relation="replaced_by")
# Hurt, Assault, Criminal Trespass
self.G.add_edge("IPC_319", "IPC_323", relation="defines_simple_hurt")
self.G.add_edge("IPC_323", "BNS_115", relation="replaced_by")
self.G.add_edge("IPC_324", "BNS_118", relation="replaced_by")
self.G.add_edge("IPC_320", "IPC_325", relation="defines_grievous_hurt")
self.G.add_edge("IPC_325", "BNS_117", relation="replaced_by")
self.G.add_edge("IPC_441", "IPC_447", relation="defines_criminal_trespass")
self.G.add_edge("IPC_447", "BNS_329", relation="replaced_by")
self.G.add_edge("IPC_425", "IPC_427", relation="defines_mischief_and_crop_damage")
self.G.add_edge("IPC_427", "BNS_324", relation="replaced_by")
self.G.add_edge("IPC_503", "IPC_506", relation="defines_criminal_intimidation")
self.G.add_edge("IPC_506", "BNS_351", relation="replaced_by")
# Matrimonial & Women Offences
self.G.add_edge("IPC_498A", "BNS_85", relation="replaced_by")
self.G.add_edge("IPC_498A", "BNS_86", relation="replaced_by")
self.G.add_edge("IPC_498A", "Arnesh_Kumar_2014", relation="governed_by_arrest_guidelines")
self.G.add_edge("IPC_304B", "BNS_80", relation="replaced_by")
self.G.add_edge("IPC_354", "BNS_74", relation="replaced_by")
self.G.add_edge("IPC_354D", "BNS_78", relation="replaced_by")
self.G.add_edge("IPC_375", "BNS_63", relation="replaced_by")
self.G.add_edge("IPC_376", "BNS_64", relation="replaced_by")
# Defamation & Conspiracy
self.G.add_edge("IPC_499", "IPC_500", relation="defines_defamation")
self.G.add_edge("IPC_500", "BNS_356", relation="replaced_by")
self.G.add_edge("IPC_120A", "IPC_120B", relation="defines_conspiracy")
self.G.add_edge("IPC_120B", "BNS_61", relation="replaced_by")
# ── 4. CRIMINAL PROCEDURE (BNSS 2023 / CrPC 1973) ──
# FIR & Police Escalation Ladder
self.G.add_edge("FIR", "CrPC_154", relation="filed_under")
self.G.add_edge("CrPC_154", "BNSS_173", relation="replaced_by")
self.G.add_edge("Zero_FIR", "BNSS_173_1", relation="mandated_under")
self.G.add_edge("Police_Refusal_FIR","CrPC_154_3", relation="escalate_to_SP")
self.G.add_edge("CrPC_154_3", "BNSS_173_4", relation="replaced_by")
self.G.add_edge("SP_Refusal", "CrPC_156_3", relation="apply_to_Magistrate")
self.G.add_edge("CrPC_156_3", "BNSS_175_3", relation="replaced_by")
self.G.add_edge("CrPC_156_3", "FIR_Registration_Order", relation="empowers_magistrate")
# Investigation, Chargesheet & Trial
self.G.add_edge("FIR", "Police_Investigation", relation="triggers")
self.G.add_edge("Police_Investigation", "Chargesheet", relation="leads_to")
self.G.add_edge("Chargesheet", "CrPC_173", relation="filed_under")
self.G.add_edge("CrPC_173", "BNSS_193", relation="replaced_by")
self.G.add_edge("Chargesheet", "Magistrate_Cognizance", relation="leads_to")
self.G.add_edge("Magistrate_Cognizance", "Trial", relation="leads_to")
self.G.add_edge("Trial", "Judgment", relation="leads_to")
self.G.add_edge("Judgment", "Appeal", relation="appealable_in")
# Land & Possession Peace Disputes (Executive Magistrate)
self.G.add_edge("Land_Breach_of_Peace", "CrPC_145", relation="governed_by")
self.G.add_edge("CrPC_145", "BNSS_164", relation="replaced_by")
self.G.add_edge("CrPC_145", "Possession_Restoration", relation="empowers_SDM")
self.G.add_edge("CrPC_145_3", "Crop_Protection_Order", relation="preserves_perishable_crops")
self.G.add_edge("Right_of_Way_Dispute", "CrPC_147", relation="governed_by")
self.G.add_edge("CrPC_147", "BNSS_166", relation="replaced_by")
# Inherent Quashing Powers
self.G.add_edge("Illegal_FIR", "CrPC_482", relation="quashing_petition")
self.G.add_edge("CrPC_482", "BNSS_528", relation="replaced_by")
self.G.add_edge("BNSS_528", "High_Court", relation="filed_before")
# Arrest, Remand & Bail
self.G.add_edge("CrPC_41", "BNSS_35", relation="replaced_by")
self.G.add_edge("CrPC_41A", "BNSS_35_3", relation="notice_of_appearance")
self.G.add_edge("CrPC_57", "BNSS_58", relation="24_hour_magistrate_production")
self.G.add_edge("CrPC_167", "BNSS_187", relation="remand_and_default_bail")
self.G.add_edge("CrPC_436", "BNSS_478", relation="bailable_offence_bail")
self.G.add_edge("CrPC_437", "BNSS_479", relation="non_bailable_offence_bail")
self.G.add_edge("CrPC_438", "BNSS_482", relation="anticipatory_bail")
self.G.add_edge("CrPC_439", "BNSS_483", relation="special_bail_powers_sessions_hc")
# Maintenance under Criminal Code
self.G.add_edge("Wife_Maintenance", "CrPC_125", relation="filed_under")
self.G.add_edge("CrPC_125", "BNSS_144", relation="replaced_by")
self.G.add_edge("BNSS_144", "Interim_Maintenance_Order", relation="empowers_court")
# ── 5. CIVIL PROCEDURE CODE (CPC 1908) & SPECIFIC RELIEF ──
self.G.add_edge("CPC_Suit", "CPC_Summons", relation="triggers")
self.G.add_edge("CPC_Summons", "CPC_Written_Statement", relation="leads_to_30_to_90_days")
self.G.add_edge("CPC_Written_Statement", "CPC_Trial", relation="leads_to")
self.G.add_edge("CPC_Trial", "CPC_Decree", relation="leads_to")
self.G.add_edge("CPC_Decree", "CPC_Appeal_S96", relation="first_appeal")
self.G.add_edge("CPC_Appeal_S96", "CPC_Second_Appeal_S100", relation="second_appeal_on_substantial_law")
self.G.add_edge("Defective_Plaint", "Order_7_Rule_11_CPC", relation="mandatory_rejection")
self.G.add_edge("Order_7_Rule_11_CPC", "Limitation_Act_1963", relation="checks_limitation")
self.G.add_edge("Order_7_Rule_11_CPC", "Court_Fees_Act_1870", relation="checks_valuation")
self.G.add_edge("Stay_Order_Request","Order_39_Rule_1_2_CPC", relation="temporary_injunction")
self.G.add_edge("Order_39_Rule_1_2_CPC", "Prima_Facie_Case", relation="requires")
self.G.add_edge("Order_39_Rule_1_2_CPC", "Balance_of_Convenience", relation="requires")
self.G.add_edge("Order_39_Rule_1_2_CPC", "Irreparable_Injury", relation="requires")
self.G.add_edge("Debt_Recovery_Suit","Order_37_CPC", relation="summary_suit")
self.G.add_edge("ADR_Settlement", "Section_89_CPC", relation="mediation_arbitration_lok_adalat")
# ── 6. FAMILY, MARRIAGE & SUCCESSION LAW ──
# Hindu Marriage Act, 1955
self.G.add_edge("HMA_13_1_i", "Adultery_Ground", relation="civil_ground_for_divorce")
self.G.add_edge("HMA_13_1_ia", "Cruelty_Ground", relation="ground_for_divorce")
self.G.add_edge("HMA_13_1_ib", "Desertion_Ground", relation="2_year_desertion_ground")
self.G.add_edge("HMA_13B", "Mutual_Consent_Divorce", relation="provides_for")
self.G.add_edge("Mutual_Consent_Divorce", "Amardeep_Singh_2017", relation="6_month_cooling_off_waiver")
self.G.add_edge("HMA_24", "Interim_Maintenance_Pendente_Lite", relation="provides_for")
self.G.add_edge("HMA_25", "Permanent_Alimony", relation="provides_for")
self.G.add_edge("HMA_9", "Restitution_of_Conjugal_Rights", relation="provides_for")
# Hindu Succession Act, 1956
self.G.add_edge("HSA_6", "Daughters_Coparcenary_By_Birth", relation="guarantees")
self.G.add_edge("Daughters_Coparcenary_By_Birth", "Vineeta_Sharma_2020", relation="supreme_court_benchmark")
self.G.add_edge("HSA_8", "Class_I_Legal_Heirs_Succession", relation="governs_self_acquired")
self.G.add_edge("HSA_30", "Testamentary_Will_of_Undivided_Share", relation="empowers_coparcener")
self.G.add_edge("Ancestral_Property_Dispute", "Partition_Suit", relation="remedy_before_civil_court")
# Domestic Violence Act (PWDVA 2005)
self.G.add_edge("Domestic_Violence", "PWDVA_2005_S12", relation="application_to_Magistrate")
self.G.add_edge("PWDVA_2005_S12", "PWDVA_S18", relation="protection_orders")
self.G.add_edge("PWDVA_2005_S12", "PWDVA_S19", relation="residence_orders")
self.G.add_edge("PWDVA_2005_S12", "PWDVA_S20", relation="monetary_relief")
self.G.add_edge("PWDVA_2005_S12", "PWDVA_S21", relation="temporary_custody")
# ── 7. COMMERCIAL, BANKING, LABOUR & PROPERTY ──
# Cheque Bounce (NI Act 1881)
self.G.add_edge("Cheque_Dishonour", "NI_Act_138", relation="criminal_offence")
self.G.add_edge("NI_Act_138", "NI_Act_138_b", relation="requires_15_day_legal_notice")
self.G.add_edge("NI_Act_138_b", "NI_Act_142", relation="30_day_magistrate_complaint_limitation")
# Indian Contract Act, 1872
self.G.add_edge("Non_Compete_Clause","Contract_Act_S27", relation="void_in_restraint_of_trade")
self.G.add_edge("Contract_Act_S27", "Percept_DMark_2006", relation="supreme_court_voidance_ruling")
self.G.add_edge("Contract_Breach", "Contract_Act_S73", relation="actual_loss_damages")
self.G.add_edge("Penalty_Clause", "Contract_Act_S74", relation="liquidated_damages_cap")
# Arbitration & Conciliation Act, 1996
self.G.add_edge("Arbitration_Interim_Relief", "Arbitration_Act_S9", relation="filed_before_court")
self.G.add_edge("Arbitrator_Appointment", "Arbitration_Act_S11", relation="high_court_petition")
self.G.add_edge("Unilateral_Arbitrator", "Arbitration_Act_S12_5", relation="void_ab_initio_TRF_Perkins")
self.G.add_edge("Arbitral_Award_Challenge", "Arbitration_Act_S34", relation="setting_aside_petition")
# Real Estate & Tenancy (RERA & TPA)
self.G.add_edge("Delayed_Flat_Possession", "RERA_2016_S18", relation="refund_with_interest")
self.G.add_edge("Tenant_Eviction", "TPA_1882_S106", relation="15_day_notice_to_quit")
self.G.add_edge("Unregistered_Lease_Over_11_Months", "Registration_Act_S17", relation="inadmissible_as_lease")
# Labour & Employment
self.G.add_edge("Unpaid_Salary_Claim", "Payment_of_Wages_Act_S15", relation="claim_before_authority")
self.G.add_edge("Minimum_Wages_Violation", "Minimum_Wages_Act_S22", relation="claim_before_Labour_Court")
self.G.add_edge("Gratuity_Eligibility", "Payment_of_Gratuity_Act_S4", relation="5_years_continuous_service")
# ── 8. STRUCK DOWN / UNCONSTITUTIONAL LANDMARK PRECEDENTS ──
self.G.add_edge("ITA_66A", "Shreya_Singhal_2015", relation="struck_down_free_speech")
self.G.add_edge("IPC_497", "Joseph_Shine_2018", relation="struck_down_gender_equality")
self.G.add_edge("IPC_377", "Navtej_Johar_2018", relation="read_down_consensual_sex")
self.G.add_edge("IPC_124A", "Vombatkere_2022", relation="stayed_and_omitted_in_BNS")
self.G.add_edge("IPC_303", "Mithu_Punjab_1983", relation="struck_down_mandatory_death")
self.G.add_edge("Aadhaar_Act_S57", "Puttaswamy_2018", relation="struck_down_private_mandate")
self.G.add_edge("Electoral_Bonds", "ADR_v_UOI_2024", relation="struck_down_right_to_know")
def _build_section_node(self, act_name: str, section_num: str) -> str:
canonical_act = normalize_act_name(act_name)
clean_sec = str(section_num).strip().replace(" ", "_")
return f"{canonical_act}_{clean_sec}"
def get_related_sections(self, act_name: str, section_num: str) -> List[Dict]:
node = self._build_section_node(act_name, section_num)
if not self.G.has_node(node):
return []
related = []
for neighbor in self.G.neighbors(node):
edge_data = self.G.get_edge_data(node, neighbor)
relation = edge_data.get("relation", "related_to")
related.append({
"source": node,
"target": neighbor,
"relation": relation,
"detail": edge_data
})
for predecessor in self.G.predecessors(node):
edge_data = self.G.get_edge_data(predecessor, node)
relation = edge_data.get("relation", "related_to")
related.append({
"source": predecessor,
"target": node,
"relation": f"is_{relation}_of",
"detail": edge_data
})
return related
def get_act_replacement(self, act_name: str) -> Dict:
canonical = normalize_act_name(act_name)
if not self.G.has_node(canonical):
return {}
for neighbor in self.G.neighbors(canonical):
edge_data = self.G.get_edge_data(canonical, neighbor)
if edge_data.get("relation") == "replaced_by":
return {
"old_act": canonical,
"new_act": neighbor,
"date": edge_data.get("date", "2024-07-01"),
"relation": "replaced_by"
}
return {}
def get_act_status(self, act_name: str) -> str:
replacement = self.get_act_replacement(act_name)
if replacement:
return "repealed"
return "active"
def get_constitutional_context(self, act_name: str) -> List[str]:
canonical = normalize_act_name(act_name)
context = []
if not self.G.has_node(canonical):
return context
for pred in self.G.predecessors(canonical):
edge_data = self.G.get_edge_data(pred, canonical)
if "override" in edge_data.get("relation", ""):
context.append(f"{pred} overrides {canonical} in constitutional conflict")
elif edge_data.get("relation") == "governs":
context.append(f"{pred} governs procedure under {canonical}")
return context
def get_procedure_chain(self, start_step: str) -> List[str]:
if not self.G.has_node(start_step):
return []
chain = [start_step]
current = start_step
visited = {start_step}
while True:
next_steps = [
n for n in self.G.neighbors(current)
if self.G.get_edge_data(current, n).get("relation")
in ["triggers", "leads_to", "next_step", "remedy_before_civil_court", "first_appeal", "appealable_in"]
and n not in visited
]
if not next_steps:
break
current = next_steps[0]
visited.add(current)
chain.append(current)
return chain
def get_struck_down_info(self, section_node: str) -> Dict:
if not self.G.has_node(section_node):
return {}
for neighbor in self.G.neighbors(section_node):
edge_data = self.G.get_edge_data(section_node, neighbor)
relation = edge_data.get("relation", "")
if "struck_down" in relation or "read_down" in relation or "stay" in relation:
return {
"section": section_node,
"case": neighbor,
"status": relation
}
return {}
def expand_context(self, layer2_results: List[Dict]) -> List[str]:
flat_insights = []
seen_acts = set()
seen_sections = set()
seed_nodes = []
for res in layer2_results:
raw_act = res.get("act_name", "")
raw_section = str(res.get("section_number", ""))
canonical_act = normalize_act_name(raw_act)
section_node = self._build_section_node(canonical_act, raw_section)
if canonical_act not in seen_acts:
seen_acts.add(canonical_act)
seed_nodes.append(canonical_act)
repl = self.get_act_replacement(canonical_act)
if repl:
flat_insights.append(
f"[LAW UPDATE] {repl['old_act']} has been replaced "
f"by {repl['new_act']} (effective {repl['date']}). "
f"Please refer to current legislation."
)
const_ctx = self.get_constitutional_context(canonical_act)
flat_insights.extend(const_ctx)
if section_node not in seen_sections:
seen_sections.add(section_node)
seed_nodes.append(section_node)
struck = self.get_struck_down_info(section_node)
if struck:
flat_insights.append(
f"[STRUCK DOWN WARNING] {struck['section']} is {struck['status']} "
f"in {struck['case']}. It is no longer valid law."
)
related = self.get_related_sections(canonical_act, raw_section)
for rel in related:
flat_insights.append(
f"Related: {rel['source']} {rel['relation']} {rel['target']}"
)
# ─── GNN MULTI-HOP PATHWAY DISCOVERY ───
try:
from gnn_engine import gnn_engine
gnn_prompt = gnn_engine.format_pathways_for_prompt(seed_nodes)
if gnn_prompt:
flat_insights.append(gnn_prompt)
except Exception as e:
print(f"[Graph] GNN pathway discovery note: {e}")
return flat_insights
# Singleton instance
legal_graph = LegalKnowledgeGraph()
if __name__ == "__main__":
print(f"Total Graph Nodes: {legal_graph.G.number_of_nodes()}")
print(f"Total Graph Edges: {legal_graph.G.number_of_edges()}")