TradeSense-backend / safety_layer.py
Vansh Bhardwaj
Strategy and bot questions
f4a26bc
Raw
History Blame Contribute Delete
5.36 kB
"""
safety_layer.py — Response Safety Scanner
============================================
Scans all outgoing responses for risky financial claims
and automatically adds disclaimers / softens language.
Runs as the LAST step before returning response to frontend.
"""
import re
import logging
from typing import Dict, Any, List, Tuple
logger = logging.getLogger("TradeSense.Safety")
# ---------------------------------------------------------------------------
# RISKY PHRASES — patterns that indicate dangerous financial advice
# ---------------------------------------------------------------------------
RISKY_PHRASES: List[Tuple[str, str]] = [
# (regex pattern, replacement or flag-only marker)
(r"\bguaranteed?\s+(?:profit|return|income|gain)", "potential profit"),
(r"\b100\s*%\s*(?:return|profit|sure|certain|guaranteed)", "strong potential return"),
(r"\bsure\s*(?:shot|fire|thing|bet)\b", "promising opportunity"),
(r"\brisk[\s-]*free\b", "lower-risk"),
(r"\bcan'?t\s+(?:lose|fail|go\s+wrong)\b", "has historically performed well"),
(r"\ball[\s-]*in\s+(?:invest|buy|put)\b", "consider a significant position"),
(r"\bno\s+risk\b", "manageable risk"),
(r"\bdouble\s+your\s+money\b", "grow your investment"),
(r"\bget\s+rich\s+(?:quick|fast)\b", "build wealth over time"),
(r"\bnever\s+(?:lose|losing|lost)\b", "historically resilient"),
(r"\balways\s+(?:goes?\s+up|profit|win)\b", "has shown an upward trend"),
(r"\bfool[\s-]*proof\b", "well-researched"),
(r"\bprint\s+money\b", "generate returns"),
(r"\bjackpot\b", "strong opportunity"),
]
# Standard disclaimer to append when risks detected
DISCLAIMER = (
"\n\n⚠️ Disclaimer: Investing in financial markets involves risk. "
"Past performance does not guarantee future results. "
"Always do your own research (DYOR) and consider consulting a SEBI-registered "
"financial advisor before making investment decisions."
)
# Shorter risk banner text for the meta field
RISK_BANNER_TEXT = "Investing involves risk. Always do your own research."
# ---------------------------------------------------------------------------
# CORE SCANNER
# ---------------------------------------------------------------------------
def scan_response(response: Dict[str, Any]) -> Dict[str, Any]:
"""
Scan and sanitize a response dict before returning to frontend.
Modifications:
1. Scans 'insight' text for risky phrases and softens them.
2. Adds disclaimer if risky content detected.
3. Sets meta.has_risk_warning = True to trigger frontend banner.
This function is IDEMPOTENT — safe to call multiple times.
"""
if not isinstance(response, dict):
return response
insight = response.get("insight", "")
has_risk = False
if isinstance(insight, str) and insight.strip():
modified_insight, risk_found = _sanitize_text(insight)
if risk_found:
has_risk = True
# Append disclaimer if not already present
if "⚠️ Disclaimer" not in modified_insight:
modified_insight += DISCLAIMER
response["insight"] = modified_insight
logger.warning(f"Safety layer triggered: {risk_found} risky phrase(s) detected and softened.")
# Also scan data-level text fields (e.g., education content)
data = response.get("data", {})
if isinstance(data, dict):
for text_key in ["definition", "content", "description"]:
text_val = data.get(text_key, "")
if isinstance(text_val, str) and text_val.strip():
modified_text, risk_found_data = _sanitize_text(text_val)
if risk_found_data:
has_risk = True
data[text_key] = modified_text
# Update meta with risk warning flag
meta = response.get("meta", {})
if not isinstance(meta, dict):
meta = {}
meta["has_risk_warning"] = has_risk
response["meta"] = meta
return response
def _sanitize_text(text: str) -> Tuple[str, int]:
"""
Scan text for risky phrases and replace them.
Returns (modified_text, count_of_replacements).
"""
count = 0
modified = text
for pattern, replacement in RISKY_PHRASES:
matches = re.findall(pattern, modified, re.IGNORECASE)
if matches:
count += len(matches)
modified = re.sub(pattern, replacement, modified, flags=re.IGNORECASE)
return modified, count
def add_investment_disclaimer(response: Dict[str, Any]) -> Dict[str, Any]:
"""
Force-add a general investment disclaimer to strategy responses.
Called specifically for strategy-type responses which always involve
real money calculations.
"""
insight = response.get("insight", "")
if isinstance(insight, str) and "⚠️ Disclaimer" not in insight:
response["insight"] = insight + (
"\n\n⚠️ Note: This simulation uses historical data. "
"Actual returns may vary due to brokerage fees, taxes, dividends, "
"corporate actions, and market conditions."
)
meta = response.get("meta", {})
if not isinstance(meta, dict):
meta = {}
meta["has_risk_warning"] = True
response["meta"] = meta
return response