| import re |
| import os |
| import urllib.parse |
|
|
|
|
| |
| INDIA_KEYWORDS = [ |
| "gst", "cgst", "sgst", "igst", "tds", "tcs", "itc", "sebi", "rbi", "india", "indian", |
| "rupee", "inr", "crore", "lakh", "section 194", "companies act", "ipc", "ibc", |
| "income tax", "delhi", "mumbai", "bombay", "chennai", "bangalore", "hyderabad", |
| "goods and services tax", "finance act", "cbdt", "gstr", "pan", "tan", |
| "fema", "rera", "msme", "nri", "oci", "itat", "nclt", "nclat" |
| ] |
| UAE_KEYWORDS = [ |
| "vat", "uae vat", "fta", "uae", "dubai", "abu dhabi", "sharjah", "ajman", |
| "dirham", "aed", "free zone", "difc", "adgm", "excise tax", |
| "federal tax authority", "corporate tax", "uae corporate", "ministry of finance", |
| "cabinet decision", "federal decree", "mainland uae", "freezone" |
| ] |
|
|
| def detect_jurisdiction(query: str) -> str: |
| q = query.lower() |
| india = sum(1 for kw in INDIA_KEYWORDS if kw in q) |
| uae = sum(1 for kw in UAE_KEYWORDS if kw in q) |
| if india > 0 and uae == 0: return "India" |
| if uae > 0 and india == 0: return "UAE" |
| return "Both" |
|
|
| |
| COMPLEX_TERMS = [ |
| "section", "act", "regulation", "notification", "circular", "judgment", |
| "case", "versus", "liability", "exemption", "penalty", "compliance", |
| "provision", "clause", "amendment", "appeal", "tribunal", "writ", |
| "holding", "ratio", "precedent", "article", "schedule" |
| ] |
|
|
| def auto_context_depth(query: str) -> int: |
| wc = len(query.split()) |
| hits = sum(1 for t in COMPLEX_TERMS if t in query.lower()) |
| if wc < 12 and hits < 2: return 5 |
| if wc < 30 and hits < 4: return 8 |
| if wc < 60 or hits < 6: return 12 |
| return 15 |
|
|
| |
| def tier_sources(docs: list) -> str: |
| if not docs: return "SYNTHESIZED" |
| max_score = max(d.get("rerank_score", d.get("score", 0)) for d in docs) |
| if max_score > 0.4: return "GROUNDED" |
| if max_score > 0.12: return "PARTIAL" |
| return "SYNTHESIZED" |
|
|
| |
| def strip_think_tags(token: str, in_think: bool) -> tuple[str, bool]: |
| output = "" |
| remaining = token |
| while remaining: |
| if in_think: |
| end_idx = remaining.find("</think>") |
| if end_idx == -1: |
| remaining = "" |
| else: |
| in_think = False |
| remaining = remaining[end_idx + len("</think>"):] |
| else: |
| start_idx = remaining.find("<think>") |
| if start_idx == -1: |
| output += remaining |
| remaining = "" |
| else: |
| output += remaining[:start_idx] |
| in_think = True |
| remaining = remaining[start_idx + len("<think>"):] |
| return output, in_think |
|
|
| |
| UAE_LAW_PATTERN = r"((?:Federal\s+)?(?:Decree-)?Law\s+No\.\s*(?:\(?\d+\)?)\s+of\s+\d{4})" |
| INDIA_LAW_PATTERN = r"((?:Income[\s-]?Tax\s+Act|Companies\s+Act|GST\s+Act|Income[\s-]?Tax\s+Rules?|Indian\s+Penal\s+Code|Insolvency\s+and\s+Bankruptcy\s+Code),?\s+\d{4})" |
| INDIA_SECTION_PATTERN = r"(Section\s+\d+[A-Z]?(?:-\s*[A-Z]+)?)" |
|
|
| def parse_citations(text: str) -> str: |
| """ |
| Scans text for legal citations and wraps them in professional tagging or markdown links. |
| Avoids double wrapping already-linked text. |
| """ |
| |
| uae_combined = r"(\[[^\]]+\]\([^\)]+\))|" + UAE_LAW_PATTERN |
| def uae_repl(match): |
| if match.group(1): |
| return match.group(1) |
| val = match.group(2) |
| encoded = urllib.parse.quote(val) |
| return f"[{val}](https://elaws.moj.gov.ae/UAE-Legislations-Search-en.aspx?query={encoded})" |
| text = re.sub(uae_combined, uae_repl, text) |
| |
| |
| india_combined = r"(\[[^\]]+\]\([^\)]+\))|" + INDIA_LAW_PATTERN |
| def india_repl(match): |
| if match.group(1): |
| return match.group(1) |
| val = match.group(2) |
| encoded = urllib.parse.quote(val) |
| return f"[{val}](https://indiankanoon.org/search/?formInput={encoded})" |
| text = re.sub(india_combined, india_repl, text) |
| |
| |
| section_combined = r"(\[[^\]]+\]\([^\)]+\))|" + INDIA_SECTION_PATTERN |
| def section_repl(match): |
| if match.group(1): |
| return match.group(1) |
| val = match.group(2) |
| encoded = urllib.parse.quote(val) |
| return f"[{val}](https://indiankanoon.org/search/?formInput={encoded})" |
| text = re.sub(section_combined, section_repl, text) |
| |
| return text |
|
|
| def format_score(score: float) -> str: |
| """Formats a search score into a user-friendly percentage or indicator.""" |
| if score > 0.8: return "High" |
| if score > 0.5: return "Medium" |
| return "Low" |
|
|