CI / ruff · pytest (pull_request) Successful in 2m53s
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>
127 lines
2.6 KiB
Python
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"))))
|