|
|
|
|
|
|
| from __future__ import annotations
|
| import re
|
|
|
|
|
| RISKY_KEYWORDS = [
|
|
|
| "gross negligence", "wilful misconduct", "liquidated damages",
|
| "sole discretion", "without liability", "no obligation",
|
| "cap on liability", "shall not exceed", "unconditionally",
|
|
|
| "personal data", "personal information", "sensitive personal",
|
| "collect and process", "data subject", "user data", "customer data",
|
| "health data", "financial data", "biometric", "aadhaar",
|
|
|
| "all intellectual property", "assigns all ip", "belongs exclusively",
|
| "vests in", "pre-existing", "background ip",
|
|
|
| "may terminate", "without cause", "at will", "unilaterally",
|
| "irrevocable", "perpetual", "non-terminable",
|
| "non-compete", "shall not compete", "no-solicit",
|
| "waive right to sue", "shall not bring action",
|
| "contingent on", "wagering", "speculative",
|
|
|
| "no notice required", "immediate termination", "forthwith",
|
| ]
|
|
|
| HIGHLIGHT_OPEN = "**"
|
| HIGHLIGHT_CLOSE = "**"
|
|
|
|
|
| def highlight_keywords(text: str) -> str:
|
| """
|
| Wrap risky keywords in Markdown bold for Gradio display.
|
| Case-insensitive, longest-match-first to avoid partial overlaps.
|
| """
|
| sorted_kw = sorted(RISKY_KEYWORDS, key=len, reverse=True)
|
| result = text
|
|
|
| for kw in sorted_kw:
|
| pattern = re.compile(re.escape(kw), re.IGNORECASE)
|
|
|
| result = pattern.sub(
|
| lambda m: f"{HIGHLIGHT_OPEN}{m.group(0)}{HIGHLIGHT_CLOSE}"
|
| if HIGHLIGHT_OPEN not in text[max(0, m.start()-2): m.start()]
|
| else m.group(0),
|
| result,
|
| )
|
| return result
|
|
|
|
|
| def format_triggered_rules(rules: list) -> str:
|
| """Return a Markdown-formatted string of triggered rules."""
|
| if not rules:
|
| return "β
No Indian-law violations detected."
|
| lines = []
|
| for r in rules:
|
| lines.append(
|
| f"- β οΈ **[{r['rule_id']}] {r['name']}** \n"
|
| f" *{r['reference']}* β penalty weight: `{r['penalty']}`"
|
| )
|
| return "\n".join(lines)
|
|
|
|
|
| def format_explanation(explanation: dict) -> str:
|
| """Return a Markdown-formatted full explanation block."""
|
| parts = [f"### π Overview\n{explanation['overview']}"]
|
|
|
| if explanation.get("rules"):
|
| parts.append("### βοΈ Rule-by-Rule Breakdown")
|
| for r in explanation["rules"]:
|
| parts.append(
|
| f"**[{r['rule_id']}] {r['name']}** *(ref: {r['reference']})*\n\n"
|
| f"- **Why it's flagged:** {r['why']}\n"
|
| f"- **What it means:** {r['meaning']}\n"
|
| f"- **Suggestion:** {r['suggestion']}"
|
| )
|
|
|
| if explanation.get("general_tip"):
|
| parts.append(f"### π‘ General Guidance\n{explanation['general_tip']}")
|
|
|
| return "\n\n".join(parts)
|
|
|
|
|
| def score_to_bar(score: float, width: int = 20) -> str:
|
| """Simple ASCII progress bar for risk score display."""
|
| filled = round(score * width)
|
| bar = "β" * filled + "β" * (width - filled)
|
| pct = int(score * 100)
|
| return f"`[{bar}] {pct}%`" |