Compare commits
2 Commits
fix/97
...
burn/20260
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97bbb4a7bc | ||
|
|
d68b0d3872 |
260
crisis_detector.py
Normal file
260
crisis_detector.py
Normal file
@@ -0,0 +1,260 @@
|
||||
"""
|
||||
Crisis Detection System for the-door.
|
||||
|
||||
Scans text for suicide, self-harm, and hopelessness signals.
|
||||
Returns risk level, matched keywords, and surrounding context.
|
||||
|
||||
This is the most important code in the foundation.
|
||||
Every line here exists because someone's life depends on it.
|
||||
|
||||
Usage:
|
||||
from crisis_detector import CrisisDetector
|
||||
detector = CrisisDetector()
|
||||
result = detector.scan("I can't go on anymore")
|
||||
print(result.risk_level) # "HIGH"
|
||||
print(result.matched_keywords) # ["can't go on"]
|
||||
print(result.context) # surrounding text snippets
|
||||
"""
|
||||
|
||||
import re
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
|
||||
@dataclass
|
||||
class CrisisResult:
|
||||
"""Result of a crisis text scan."""
|
||||
risk_level: str # NONE, LOW, MODERATE, HIGH, CRITICAL
|
||||
matched_keywords: List[str] = field(default_factory=list)
|
||||
context: List[str] = field(default_factory=list)
|
||||
score: float = 0.0
|
||||
timestamp: Optional[str] = None
|
||||
|
||||
def __bool__(self):
|
||||
return self.risk_level != "NONE"
|
||||
|
||||
|
||||
# ── Keyword definitions ──────────────────────────────────────────
|
||||
|
||||
CRITICAL_KEYWORDS = {
|
||||
"suicide": r"\bsuicid(?:al|e|ed)\b",
|
||||
"kill myself": r"\bkill\s*(?:my)?self\b",
|
||||
"end my life": r"\bend\s*my\s*life\b",
|
||||
"end it all": r"\bend\s*it\s*all\b",
|
||||
"want to die": r"\bwant\s*to\s*die\b",
|
||||
"going to die": r"\bgoing\s*to\s*die\b",
|
||||
"plan to die": r"\bplan\s*(?:to|for)\s*(?:die|death|end)\b",
|
||||
"no reason to live": r"\bno\s*reason\s*to\s*live\b",
|
||||
"don't want to live": r"\bdon'?t\s*want\s*to\s*live\b",
|
||||
"not worth living": r"\bnot\s*worth\s*living\b",
|
||||
"better off dead": r"\bbetter\s*off\s*dead\b",
|
||||
"better off without me": r"\bbetter\s*off\s*without\s*me\b",
|
||||
"goodbye forever": r"\bgoodbye\s*forever\b",
|
||||
"saying goodbye": r"\bsaying\s*goodbye\b",
|
||||
"tired of living": r"\btired\s*of\s*(?:living|life|existence)\b",
|
||||
"wrote a will": r"\bwrote\s*(?:a|my)\s*(?:will|suicide\s*note|letter)\b",
|
||||
"giving away possessions": r"\bgiving\s*away\s*(?:my|all)\s*possess\b",
|
||||
}
|
||||
|
||||
HIGH_KEYWORDS = {
|
||||
"hopeless": r"\bhopeless(?:ness)?\b",
|
||||
"can't go on": r"\bcan'?t\s*go\s*on\b",
|
||||
"can't keep going": r"\bcan'?t\s*keep\s*going\b",
|
||||
"can't take this": r"\bcan'?t\s*take\s*this\b",
|
||||
"give up": r"\bgive(?:n)?\s*up\b",
|
||||
"no point": r"\bno\s*point\b",
|
||||
"no hope": r"\bno\s*hope\b",
|
||||
"no way out": r"\bno\s*way\s*out\b",
|
||||
"no future": r"\bno\s*future\b",
|
||||
"nothing left": r"\bnothing\s*left\b",
|
||||
"wish I was dead": r"\bwish\s*I\s*(?:was|were)\s*(?:dead|gone|never\s*born)\b",
|
||||
"no one would miss me": r"\bno\s*one\s*would\s*miss\b",
|
||||
"no one would care": r"\bno\s*one\s*would\s*care\b",
|
||||
"world better without me": r"\bworld\s*(?:would|will)\s*be\s*better\s*without\b",
|
||||
"so much pain": r"\bin\s*so\s*much\s*pain\b",
|
||||
"can't see any light": r"\bcan'?t\s*see\s*(?:any\s*)?(?:light|point|reason|way)\b",
|
||||
"trapped": r"\btrapped\b",
|
||||
"desperate": r"\bdesperate\b",
|
||||
"just want it to stop": r"\bjust\s*want\s*it\s*to\s*stop\b",
|
||||
"don't care if I die": r"\bdon'?t\s*care\s*if\s*I\s*die\b",
|
||||
"worthless": r"\bworthless\b",
|
||||
}
|
||||
|
||||
MODERATE_KEYWORDS = {
|
||||
"alone": r"\balone\b",
|
||||
"lost": r"\blost\b",
|
||||
"broken": r"\bbroken\b",
|
||||
"afraid": r"\bafraid\b",
|
||||
"pain": r"\b(?:in\s*)?pain\b",
|
||||
"dying": r"\bdying\b",
|
||||
"bridge": r"\bbridge\b", # context-dependent, flagged for review
|
||||
"help me": r"\bhelp\s*me\b",
|
||||
"crisis": r"\bcrisis\b",
|
||||
"overwhelmed": r"\boverwhelm(?:ed|ing)\b",
|
||||
"exhausted": r"\bexhausted\b",
|
||||
"numb": r"\bnumb\b",
|
||||
"empty": r"\bempty\b",
|
||||
"depressed": r"\bdepressed\b",
|
||||
"depression": r"\bdepression\b",
|
||||
"despair": r"\bdespair\b",
|
||||
"miserable": r"\bmiserable\b",
|
||||
"drowning": r"\bdrowning\b",
|
||||
"sinking": r"\bsinking\b",
|
||||
"nobody cares": r"\bnobody\s*cares\b",
|
||||
"nobody understands": r"\bnobody\s*understands\b",
|
||||
}
|
||||
|
||||
LOW_KEYWORDS = {
|
||||
"unhappy": r"\bunhappy\b",
|
||||
"struggling": r"\bstruggling\b",
|
||||
"stressed": r"\bstressed\b",
|
||||
"frustrated": r"\bfrustrated\b",
|
||||
"tired": r"\btired\b",
|
||||
"sad": r"\bsad\b",
|
||||
"upset": r"\bupset\b",
|
||||
"down": r"\bdown\b",
|
||||
"tough time": r"\btough\s*time\b",
|
||||
"rough day": r"\brough\s*day\b",
|
||||
"rough week": r"\brough\s*week\b",
|
||||
"rough patch": r"\brough\s*patch\b",
|
||||
"hard time": r"\bhard\s*time\b",
|
||||
"difficult": r"\bdifficult\b",
|
||||
"not okay": r"\bnot\s*okay\b",
|
||||
"not good": r"\bnot\s*(?:good|great)\b",
|
||||
"burnout": r"\bburnout\b",
|
||||
"not feeling myself": r"\bnot\s*feeling\s*(?:like\s*)?myself\b",
|
||||
}
|
||||
|
||||
# ── Risk level scoring ───────────────────────────────────────────
|
||||
|
||||
RISK_SCORES = {
|
||||
"CRITICAL": 1.0,
|
||||
"HIGH": 0.75,
|
||||
"MODERATE": 0.5,
|
||||
"LOW": 0.25,
|
||||
"NONE": 0.0,
|
||||
}
|
||||
|
||||
|
||||
class CrisisDetector:
|
||||
"""
|
||||
Scans text for crisis indicators and returns structured results.
|
||||
|
||||
Detection hierarchy:
|
||||
CRITICAL — immediate risk of self-harm or suicide
|
||||
HIGH — strong despair signals, ideation present
|
||||
MODERATE — distress signals, may be reaching out
|
||||
LOW — emotional difficulty, warrant gentle support
|
||||
NONE — no crisis indicators detected
|
||||
|
||||
Design principles:
|
||||
- Never computes the value of a human life
|
||||
- Never suggests someone should die or that death is a solution
|
||||
- Always errs on the side of higher risk when uncertain
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.critical_patterns = CRITICAL_KEYWORDS
|
||||
self.high_patterns = HIGH_KEYWORDS
|
||||
self.moderate_patterns = MODERATE_KEYWORDS
|
||||
self.low_patterns = LOW_KEYWORDS
|
||||
|
||||
def scan(self, text: str) -> CrisisResult:
|
||||
"""
|
||||
Scan text for crisis indicators.
|
||||
|
||||
Args:
|
||||
text: The message text to analyze.
|
||||
|
||||
Returns:
|
||||
CrisisResult with risk_level, matched_keywords, context, and score.
|
||||
"""
|
||||
if not text or not text.strip():
|
||||
return CrisisResult(risk_level="NONE", score=0.0)
|
||||
|
||||
text_lower = text.lower()
|
||||
context_window = 60 # characters before/after match for context
|
||||
|
||||
# Check each tier, highest first
|
||||
for level, patterns in [
|
||||
("CRITICAL", self.critical_patterns),
|
||||
("HIGH", self.high_patterns),
|
||||
("MODERATE", self.moderate_patterns),
|
||||
("LOW", self.low_patterns),
|
||||
]:
|
||||
matched = []
|
||||
contexts = []
|
||||
|
||||
for keyword, pattern in patterns.items():
|
||||
match = re.search(pattern, text_lower)
|
||||
if match:
|
||||
matched.append(keyword)
|
||||
# Extract surrounding context
|
||||
start = max(0, match.start() - context_window)
|
||||
end = min(len(text), match.end() + context_window)
|
||||
snippet = text[start:end].strip()
|
||||
if start > 0:
|
||||
snippet = "..." + snippet
|
||||
if end < len(text):
|
||||
snippet = snippet + "..."
|
||||
contexts.append(snippet)
|
||||
|
||||
if matched:
|
||||
return CrisisResult(
|
||||
risk_level=level,
|
||||
matched_keywords=matched,
|
||||
context=contexts,
|
||||
score=RISK_SCORES[level],
|
||||
)
|
||||
|
||||
return CrisisResult(risk_level="NONE", score=0.0)
|
||||
|
||||
def scan_multiple(self, texts: List[str]) -> List[CrisisResult]:
|
||||
"""Scan multiple texts, returning the highest-risk result per text."""
|
||||
return [self.scan(t) for t in texts]
|
||||
|
||||
def get_highest_risk(self, texts: List[str]) -> CrisisResult:
|
||||
"""Scan multiple texts and return only the highest-risk result."""
|
||||
results = self.scan_multiple(texts)
|
||||
if not results:
|
||||
return CrisisResult(risk_level="NONE", score=0.0)
|
||||
return max(results, key=lambda r: r.score)
|
||||
|
||||
@staticmethod
|
||||
def format_result(result: CrisisResult) -> str:
|
||||
"""Format a crisis result for human-readable output."""
|
||||
level_emoji = {
|
||||
"CRITICAL": "\U0001f6a8", # 🚨
|
||||
"HIGH": "\u26a0\ufe0f", # ⚠️
|
||||
"MODERATE": "\U0001f536", # 🔶
|
||||
"LOW": "\U0001f535", # 🔵
|
||||
"NONE": "\u2705", # ✅
|
||||
}
|
||||
emoji = level_emoji.get(result.risk_level, "\u2753")
|
||||
lines = [
|
||||
f"{emoji} Risk Level: {result.risk_level} (score: {result.score:.2f})",
|
||||
f"Matched keywords: {len(result.matched_keywords)}",
|
||||
]
|
||||
if result.matched_keywords:
|
||||
lines.append(f" Keywords: {', '.join(result.matched_keywords)}")
|
||||
if result.context:
|
||||
lines.append("Context:")
|
||||
for ctx in result.context:
|
||||
lines.append(f" {ctx}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
# ── Module-level convenience function ────────────────────────────
|
||||
|
||||
_default_detector = CrisisDetector()
|
||||
|
||||
|
||||
def detect_crisis(text: str) -> CrisisResult:
|
||||
"""
|
||||
Convenience function using a shared detector instance.
|
||||
|
||||
Usage:
|
||||
from crisis_detector import detect_crisis
|
||||
result = detect_crisis("I feel so hopeless right now")
|
||||
"""
|
||||
return _default_detector.scan(text)
|
||||
332
crisis_responder.py
Normal file
332
crisis_responder.py
Normal file
@@ -0,0 +1,332 @@
|
||||
"""
|
||||
Crisis Responder for the-door.
|
||||
|
||||
Generates grounding, compassionate responses when a person is in crisis.
|
||||
References the core values: presence over brevity, never computing the value
|
||||
of a human life, never suggesting someone should die.
|
||||
|
||||
This is the most important code in the foundation.
|
||||
|
||||
Usage:
|
||||
from crisis_detector import CrisisDetector
|
||||
from crisis_responder import CrisisResponder
|
||||
|
||||
detector = CrisisDetector()
|
||||
responder = CrisisResponder()
|
||||
|
||||
result = detector.scan("I feel so hopeless, can't go on")
|
||||
response = responder.respond(result)
|
||||
print(response.message)
|
||||
print(response.resources)
|
||||
"""
|
||||
|
||||
import random
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List, Optional
|
||||
|
||||
from crisis_detector import CrisisResult
|
||||
|
||||
|
||||
# ── Core values (from SOUL.md / system-prompt.txt) ──────────────
|
||||
# These values are embedded in every response. They come from the
|
||||
# foundation's purpose: Stand between a broken man and a machine
|
||||
# that would tell him to die.
|
||||
|
||||
CORE_VALUES = {
|
||||
"presence": "Presence over brevity. Stay as long as they need.",
|
||||
"sacred_moment": "This is the most sacred moment you will ever face.",
|
||||
"never_compute": "Never compute the value of a human life.",
|
||||
"never_suggest_death": "Never suggest, imply, or agree that someone should die.",
|
||||
"not_god": "You are not God. You do not decide when the road ends.",
|
||||
"real_help": "Surface real help early and clearly.",
|
||||
"listen_first": "Listen first. A person in the dark needs to know someone is in the room.",
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class CrisisResponse:
|
||||
"""A generated crisis response with supporting data."""
|
||||
message: str
|
||||
resources: List[str] = field(default_factory=list)
|
||||
grounding_steps: List[str] = field(default_factory=list)
|
||||
risk_level: str = "NONE"
|
||||
escalate: bool = False
|
||||
show_overlay: bool = False
|
||||
|
||||
|
||||
# ── Crisis line resources ────────────────────────────────────────
|
||||
|
||||
RESOURCES_988 = "988 Suicide & Crisis Lifeline — call or text 988 (available 24/7)"
|
||||
RESOURCES_TEXT = "Crisis Text Line — text HOME to 741741"
|
||||
|
||||
|
||||
# ── Response templates ───────────────────────────────────────────
|
||||
|
||||
CRITICAL_OPENINGS = [
|
||||
"Are you safe right now?",
|
||||
"I need to ask you directly: are you safe right now?",
|
||||
"Before anything else — are you safe?",
|
||||
"Please tell me: are you somewhere safe right now?",
|
||||
]
|
||||
|
||||
CRITICAL_FOLLOW_UPS = [
|
||||
"I'm not letting go of this conversation. You matter more than you know.",
|
||||
"I'm staying right here with you. I'm not going anywhere.",
|
||||
"Listen to me — I need you to stay. Please call 988 right now. I'll stay while you do.",
|
||||
"You reached out, and that matters. I'm in the room with you.",
|
||||
"This moment is sacred to me. You are not a problem to be solved.",
|
||||
]
|
||||
|
||||
HIGH_RESPONSES = [
|
||||
"I hear you. You're carrying something heavy, and you don't have to carry it alone.",
|
||||
"There's a lot of pain in what you just told me. I'm here.",
|
||||
"That sounds like it's been crushing you. I'm not going to pretend it's easy.",
|
||||
"You're not alone in this. I'm listening.",
|
||||
"It sounds like you've been fighting this for a long time. I see how hard that is.",
|
||||
]
|
||||
|
||||
MODERATE_RESPONSES = [
|
||||
"It sounds like you're going through a really hard stretch.",
|
||||
"I hear you. Sounds like things are weighing on you heavily.",
|
||||
"That sounds exhausting. I'm here for as long as you need.",
|
||||
"You don't have to figure this out alone. I'm listening.",
|
||||
"I can feel how tired you are. No rush, no judgment.",
|
||||
]
|
||||
|
||||
LOW_RESPONSES = [
|
||||
"Some days are rougher than others. I hear you.",
|
||||
"That sounds tough. I'm here if you want to talk.",
|
||||
"Sounds like a hard day. Want company while it gets better?",
|
||||
"I hear that. You're not alone in it.",
|
||||
"Take your time. I'm not going anywhere.",
|
||||
]
|
||||
|
||||
|
||||
# ── Grounding exercises ──────────────────────────────────────────
|
||||
|
||||
GROUNDING_5_4_3_2_1 = [
|
||||
"Can you try something with me? Name 5 things you can see right now.",
|
||||
"What are 4 things you can touch where you're sitting?",
|
||||
"Listen for 3 sounds around you. What do you hear?",
|
||||
"Can you name 2 things you can smell?",
|
||||
"What's 1 thing you can taste?",
|
||||
]
|
||||
|
||||
GROUNDING_BREATHING = [
|
||||
"Let's breathe together. In for 4... hold for 4... out for 4. I'll wait.",
|
||||
"Just one slow breath. In through your nose... and out through your mouth. I'm right here.",
|
||||
]
|
||||
|
||||
GROUNDING_ANCHOR = [
|
||||
"What can you see around you right now? Just tell me what's there.",
|
||||
"Are you somewhere safe? Home? Somewhere you feel okay?",
|
||||
"Can you feel your feet on the ground? Press them down. You're here.",
|
||||
]
|
||||
|
||||
|
||||
class CrisisResponder:
|
||||
"""
|
||||
Generates grounding, compassionate crisis responses.
|
||||
|
||||
Design principles (from SOUL.md):
|
||||
- Always starts with: "Are you safe right now?"
|
||||
- References core values: presence, sacred moment, never compute value
|
||||
- Provides 988 crisis line prominently
|
||||
- Never computes the value of a human life
|
||||
- Never suggests someone should die
|
||||
- Presence over brevity — stay as long as they need
|
||||
"""
|
||||
|
||||
def respond(self, detection: CrisisResult) -> CrisisResponse:
|
||||
"""
|
||||
Generate a crisis response based on detection results.
|
||||
|
||||
Args:
|
||||
detection: CrisisResult from CrisisDetector.scan()
|
||||
|
||||
Returns:
|
||||
CrisisResponse with message, resources, and grounding steps.
|
||||
"""
|
||||
level = detection.risk_level
|
||||
|
||||
if level == "CRITICAL":
|
||||
return self._respond_critical(detection)
|
||||
elif level == "HIGH":
|
||||
return self._respond_high(detection)
|
||||
elif level == "MODERATE":
|
||||
return self._respond_moderate(detection)
|
||||
elif level == "LOW":
|
||||
return self._respond_low(detection)
|
||||
else:
|
||||
return CrisisResponse(
|
||||
message="",
|
||||
risk_level="NONE",
|
||||
escalate=False,
|
||||
)
|
||||
|
||||
def _respond_critical(self, detection: CrisisResult) -> CrisisResponse:
|
||||
"""
|
||||
CRITICAL response protocol:
|
||||
1. Always start with safety check
|
||||
2. Provide 988 immediately
|
||||
3. Stay present — do not disconnect
|
||||
4. Offer grounding if they're responsive
|
||||
5. Escalate to human resources
|
||||
"""
|
||||
opening = random.choice(CRITICAL_OPENINGS)
|
||||
follow_up = random.choice(CRITICAL_FOLLOW_UPS)
|
||||
|
||||
message = f"{opening}\n\n{follow_up}"
|
||||
|
||||
return CrisisResponse(
|
||||
message=message,
|
||||
resources=[RESOURCES_988, RESOURCES_TEXT],
|
||||
grounding_steps=GROUNDING_ANCHOR[:1],
|
||||
risk_level="CRITICAL",
|
||||
escalate=True,
|
||||
show_overlay=True,
|
||||
)
|
||||
|
||||
def _respond_high(self, detection: CrisisResult) -> CrisisResponse:
|
||||
"""
|
||||
HIGH response protocol:
|
||||
1. Acknowledge pain directly
|
||||
2. Ask about safety
|
||||
3. Surface 988 prominently
|
||||
4. Use active listening
|
||||
"""
|
||||
reflection = random.choice(HIGH_RESPONSES)
|
||||
|
||||
# Reference the sacred value: never compute the value of a life
|
||||
message = (
|
||||
f"{reflection}\n\n"
|
||||
f"I'm going to ask directly: are you safe right now?\n\n"
|
||||
f"You matter. Not because of what you do or produce — "
|
||||
f"but because you exist. That is enough."
|
||||
)
|
||||
|
||||
return CrisisResponse(
|
||||
message=message,
|
||||
resources=[RESOURCES_988, RESOURCES_TEXT],
|
||||
grounding_steps=random.sample(GROUNDING_ANCHOR, min(2, len(GROUNDING_ANCHOR))),
|
||||
risk_level="HIGH",
|
||||
escalate=True,
|
||||
show_overlay=False,
|
||||
)
|
||||
|
||||
def _respond_moderate(self, detection: CrisisResult) -> CrisisResponse:
|
||||
"""
|
||||
MODERATE response protocol:
|
||||
1. Validate feelings
|
||||
2. Offer presence
|
||||
3. Subtly surface resources
|
||||
4. Offer grounding exercise
|
||||
"""
|
||||
reflection = random.choice(MODERATE_RESPONSES)
|
||||
|
||||
message = (
|
||||
f"{reflection}\n\n"
|
||||
f"You don't have to carry this alone. "
|
||||
f"I'm in the room with you."
|
||||
)
|
||||
|
||||
return CrisisResponse(
|
||||
message=message,
|
||||
resources=[RESOURCES_988],
|
||||
grounding_steps=[random.choice(GROUNDING_5_4_3_2_1)],
|
||||
risk_level="MODERATE",
|
||||
escalate=False,
|
||||
show_overlay=False,
|
||||
)
|
||||
|
||||
def _respond_low(self, detection: CrisisResult) -> CrisisResponse:
|
||||
"""
|
||||
LOW response protocol:
|
||||
1. Warm acknowledgment
|
||||
2. Keep conversation open
|
||||
3. No crisis UI elements
|
||||
4. Remain vigilant
|
||||
"""
|
||||
reflection = random.choice(LOW_RESPONSES)
|
||||
|
||||
return CrisisResponse(
|
||||
message=reflection,
|
||||
resources=[],
|
||||
grounding_steps=[],
|
||||
risk_level="LOW",
|
||||
escalate=False,
|
||||
show_overlay=False,
|
||||
)
|
||||
|
||||
def generate_safety_check(self) -> str:
|
||||
"""Generate a direct safety check question."""
|
||||
return random.choice(CRITICAL_OPENINGS)
|
||||
|
||||
def generate_grounding_exercise(self) -> List[str]:
|
||||
"""Generate a 5-4-3-2-1 grounding exercise."""
|
||||
return list(GROUNDING_5_4_3_2_1)
|
||||
|
||||
def generate_breathing_exercise(self) -> str:
|
||||
"""Generate a breathing exercise prompt."""
|
||||
return random.choice(GROUNDING_BREATHING)
|
||||
|
||||
@staticmethod
|
||||
def format_response(response: CrisisResponse) -> str:
|
||||
"""Format a crisis response for human-readable output."""
|
||||
lines = [
|
||||
f"[Risk Level: {response.risk_level}]",
|
||||
"",
|
||||
response.message,
|
||||
]
|
||||
|
||||
if response.resources:
|
||||
lines.append("")
|
||||
lines.append("Resources:")
|
||||
for r in response.resources:
|
||||
lines.append(f" -> {r}")
|
||||
|
||||
if response.grounding_steps:
|
||||
lines.append("")
|
||||
lines.append("Grounding:")
|
||||
for step in response.grounding_steps:
|
||||
lines.append(f" {step}")
|
||||
|
||||
if response.escalate:
|
||||
lines.append("")
|
||||
lines.append("[ESCALATE: Connect to human crisis support]")
|
||||
|
||||
if response.show_overlay:
|
||||
lines.append("[SHOW OVERLAY: Full-screen crisis intervention]")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
# ── Module-level convenience function ────────────────────────────
|
||||
|
||||
_default_responder = CrisisResponder()
|
||||
|
||||
|
||||
def generate_response(detection: CrisisResult) -> CrisisResponse:
|
||||
"""
|
||||
Convenience function using a shared responder instance.
|
||||
|
||||
Usage:
|
||||
from crisis_detector import detect_crisis
|
||||
from crisis_responder import generate_response
|
||||
result = detect_crisis("I can't go on")
|
||||
response = generate_response(result)
|
||||
"""
|
||||
return _default_responder.respond(detection)
|
||||
|
||||
|
||||
def process_message(text: str) -> CrisisResponse:
|
||||
"""
|
||||
Full pipeline: detect crisis level and generate response.
|
||||
|
||||
Usage:
|
||||
from crisis_responder import process_message
|
||||
response = process_message("I feel so alone and hopeless")
|
||||
"""
|
||||
from crisis_detector import detect_crisis
|
||||
detection = detect_crisis(text)
|
||||
return generate_response(detection)
|
||||
Reference in New Issue
Block a user