Files
PTSRaiAgent/app/dialog/texts.py
T
ElebobaandClaude Opus 5.5 c489ddbdc4
CI / ruff · pytest (pull_request) Successful in 2m53s
feat(bot): chat answers questions in any state; OpenRouter via proxy from the RF server
A question asked mid-questionnaire, mid-check-in or during a crisis is answered right away (agent →
guidelines/instruction); the pending question is re-shown with its buttons (engine remembers the last
prompt), in a crisis the answer carries the emergency contacts and the crisis stays open until the doctor's
ack. Acute messages still get the fixed crisis script. OpenRouter blocks RF IPs: OPENROUTER_PROXY_URL routes
the agent through an HTTP proxy, as vobot does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-04 17:42:03 +03:00

127 lines
2.6 KiB
Python

"""Patient-facing dialog texts from `config/texts/dialog.yaml` (ADR-008: texts live in config, not code)."""
from functools import lru_cache
from pathlib import Path
import yaml
from pydantic import BaseModel, ConfigDict, Field
from app.config import get_settings
from app.core.safety import fill_phones
class _Strict(BaseModel):
# A typo in a YAML key must fail at load time, not silently show an empty message to a patient.
model_config = ConfigDict(extra="forbid", frozen=True)
class DialogSettings(_Strict):
cssrs_cooldown_hours: float = 24
max_safety_events: int = 50
crisis_flags: list[str] = Field(default_factory=list)
risk_screen_scale: str = "cssrs_screen"
abandon_after_hours: float = 6
class Commands(_Strict):
revoke: list[str]
class ConsentTexts(_Strict):
version: str
text: str
accept: str
decline: str
refused: str
reconsider: str
code_invalid: str
class AgeGateTexts(_Strict):
question: str
adult: str
minor: str
unclear: str
minor_stop: str
class IdleButtons(_Strict):
checkin: str
med_question: str
kb_question: str
contact_doctor: str
revoke: str
class IdleTexts(_Strict):
welcome: str
menu_prompt: str
buttons: IdleButtons
stub_checkin: str
stub_med_question: str
contact_doctor_ack: str
visit_stub: str
class RevokedTexts(_Strict):
confirm: str
class CrisisTexts(_Strict):
repeat: str
hold: str
closed: str
class ScaleTexts(_Strict):
intro: str
question: str
clarify: str
buttons_only: str
done: str
risk_screen_intro: str
class SafetyPlanTexts(_Strict):
title: str
template: list[str]
means_restriction: str
class ReachOutTexts(_Strict):
silence: str
missed_doses: str
default: str
after_crisis: str
class VisitTexts(_Strict):
button: str
class DialogTexts(_Strict):
status: str
settings: DialogSettings
commands: Commands
consent: ConsentTexts
age_gate: AgeGateTexts
idle: IdleTexts
revoked: RevokedTexts
crisis: CrisisTexts
scale: ScaleTexts
safety_plan: SafetyPlanTexts
reach_out: ReachOutTexts
visit: VisitTexts
unavailable: str
side_answer_return: str = "Вернёмся к вопросу:"
def dialog_texts_path() -> Path:
return get_settings().config_dir / "texts" / "dialog.yaml"
@lru_cache
def load_dialog_texts(path: Path | None = None) -> DialogTexts:
path = path or dialog_texts_path()
return DialogTexts.model_validate(yaml.safe_load(fill_phones(path.read_text(encoding="utf-8"))))