Spaces:
Sleeping
Sleeping
| # chaplain_models.py | |
| """ | |
| Data models for Chaplain Feedback & Tagging System. | |
| Defines core data structures for classification flows, tagging records, | |
| distress indicators, and interaction logging. | |
| """ | |
| from dataclasses import dataclass, field | |
| from typing import List, Optional, Dict, Any | |
| from datetime import datetime | |
| # ============================================================================= | |
| # INDICATOR DEFINITIONS - Based on Spiritual Distress Definitions Document | |
| # ============================================================================= | |
| # Mapping of all indicators from the definitions document with their categories, | |
| # subcategories, severity (red/yellow), and definition references. | |
| # RED (#ea9999): Severe distress - requires immediate attention | |
| # YELLOW (#ffe599): Potential distress - requires clarification | |
| INDICATOR_DEFINITIONS: Dict[str, Dict[str, Any]] = { | |
| # Section II.A - Emotional expressions | |
| "crying": { | |
| "category": "Emotional", | |
| "subcategory": "Crying", | |
| "severity": "red", | |
| "definition_reference": "II.A", | |
| "description": "Crying as expression of spiritual distress" | |
| }, | |
| "dysomnias": { | |
| "category": "Emotional", | |
| "subcategory": "Dysomnias/Difficulty sleeping", | |
| "severity": "yellow", | |
| "definition_reference": "II.A", | |
| "description": "Sleep disturbances related to spiritual distress" | |
| }, | |
| "fatigue": { | |
| "category": "Emotional", | |
| "subcategory": "Fatigue, emotional exhaustion", | |
| "severity": "yellow", | |
| "definition_reference": "II.A", | |
| "description": "Fatigue and emotional exhaustion" | |
| }, | |
| "anxiety": { | |
| "category": "Emotional", | |
| "subcategory": "Anxiety", | |
| "severity": "yellow", | |
| "definition_reference": "II.A", | |
| "description": "Anxiety as expression of spiritual distress" | |
| }, | |
| "fear": { | |
| "category": "Emotional", | |
| "subcategory": "Fear", | |
| "severity": "yellow", | |
| "definition_reference": "II.A", | |
| "description": "Fear as expression of spiritual distress" | |
| }, | |
| "anger": { | |
| "category": "Emotional", | |
| "subcategory": "Anger", | |
| "severity": "red", | |
| "definition_reference": "II.A", | |
| "description": "Anger as expression of spiritual distress" | |
| }, | |
| "depressive_symptoms": { | |
| "category": "Emotional", | |
| "subcategory": "Depressive symptoms", | |
| "severity": "yellow", | |
| "definition_reference": "II.A", | |
| "description": "Depressive symptoms" | |
| }, | |
| # Section II.B - Decreased engagement | |
| "decreased_engagement": { | |
| "category": "Engagement", | |
| "subcategory": "Decreased engagement with hobbies", | |
| "severity": "yellow", | |
| "definition_reference": "II.B", | |
| "description": "Decreased engagement with hobbies, creative expression, and personal interests" | |
| }, | |
| # Section II.C - Disinterest in nature | |
| "disinterest_nature": { | |
| "category": "Engagement", | |
| "subcategory": "Disinterest in nature", | |
| "severity": "yellow", | |
| "definition_reference": "II.C", | |
| "description": "Disinterest in nature due to spiritual, emotional and physical limitations" | |
| }, | |
| # Section II.D - Excessive guilt | |
| "excessive_guilt": { | |
| "category": "Guilt", | |
| "subcategory": "Excessive guilt", | |
| "severity": "red", | |
| "definition_reference": "II.D", | |
| "description": "Excessive guilt - existential, religious, or relational" | |
| }, | |
| # Section II.E - Anger behaviors of spiritual nature | |
| "anger_spiritual": { | |
| "category": "Anger", | |
| "subcategory": "Anger behaviors of a spiritual nature", | |
| "severity": "red", | |
| "definition_reference": "II.E", | |
| "description": "Anger toward power greater than self" | |
| }, | |
| # Section II.F - Grief types | |
| "anticipatory_grieving": { | |
| "category": "Grief", | |
| "subcategory": "Anticipatory grieving", | |
| "severity": "red", | |
| "definition_reference": "II.F", | |
| "description": "Emotional response to anticipated death" | |
| }, | |
| "disenfranchised_grief": { | |
| "category": "Grief", | |
| "subcategory": "Disenfranchised grief", | |
| "severity": "red", | |
| "definition_reference": "II.F", | |
| "description": "Grief unacknowledged or unsupported by society" | |
| }, | |
| "life_review_grieving": { | |
| "category": "Grief", | |
| "subcategory": "Grieving in the setting of life review", | |
| "severity": "yellow", | |
| "definition_reference": "II.F", | |
| "description": "Grieving during life review process" | |
| }, | |
| "maladaptive_grieving": { | |
| "category": "Grief", | |
| "subcategory": "Maladaptive grieving", | |
| "severity": "red", | |
| "definition_reference": "II.F", | |
| "description": "Prolonged grief disorder" | |
| }, | |
| "complicated_grief": { | |
| "category": "Grief", | |
| "subcategory": "Complicated grief", | |
| "severity": "red", | |
| "definition_reference": "II.F", | |
| "description": "Persistent, intense grief disrupting daily life" | |
| }, | |
| "loss_loved_one": { | |
| "category": "Grief", | |
| "subcategory": "Loss of a loved one", | |
| "severity": "red", | |
| "definition_reference": "II.F", | |
| "description": "Loss of family member or friend" | |
| }, | |
| # Section II.G - Expressions of Spiritual Distress | |
| "expresses_alienation": { | |
| "category": "Expressions", | |
| "subcategory": "Expresses alienation", | |
| "severity": "yellow", | |
| "definition_reference": "II.G", | |
| "description": "Feeling separation, isolation, disconnection" | |
| }, | |
| "concern_beliefs": { | |
| "category": "Expressions", | |
| "subcategory": "Expresses concern about beliefs", | |
| "severity": "yellow", | |
| "definition_reference": "II.G", | |
| "description": "Questions or struggles with spiritual/religious beliefs" | |
| }, | |
| "concern_future": { | |
| "category": "Expressions", | |
| "subcategory": "Expresses concern about the future", | |
| "severity": "red", | |
| "definition_reference": "II.G", | |
| "description": "Anxious, fearful, or uncertain about what lies ahead" | |
| }, | |
| "concern_values": { | |
| "category": "Expressions", | |
| "subcategory": "Expresses concern about values system", | |
| "severity": "yellow", | |
| "definition_reference": "II.G", | |
| "description": "Conflicted about moral or ethical principles" | |
| }, | |
| "concern_family": { | |
| "category": "Expressions", | |
| "subcategory": "Expresses concerns about family", | |
| "severity": "yellow", | |
| "definition_reference": "II.G", | |
| "description": "Distressed about family well-being or relationships" | |
| }, | |
| "feeling_emptiness": { | |
| "category": "Expressions", | |
| "subcategory": "Expresses feeling of emptiness", | |
| "severity": "red", | |
| "definition_reference": "II.G", | |
| "description": "Deep inner void or lack of meaning" | |
| }, | |
| "feeling_unloved": { | |
| "category": "Expressions", | |
| "subcategory": "Expresses feeling unloved", | |
| "severity": "red", | |
| "definition_reference": "II.G", | |
| "description": "Feels unworthy of love or disconnected from caring relationships" | |
| }, | |
| "feeling_worthless": { | |
| "category": "Expressions", | |
| "subcategory": "Expresses feeling worthless", | |
| "severity": "red", | |
| "definition_reference": "II.G", | |
| "description": "Perceives themselves as having little or no value" | |
| }, | |
| "insufficient_courage": { | |
| "category": "Expressions", | |
| "subcategory": "Expresses insufficient courage", | |
| "severity": "yellow", | |
| "definition_reference": "II.G", | |
| "description": "Fear or lack of strength to face suffering" | |
| }, | |
| "loss_confidence": { | |
| "category": "Expressions", | |
| "subcategory": "Expresses loss of confidence", | |
| "severity": "yellow", | |
| "definition_reference": "II.G", | |
| "description": "Diminished trust in themselves or abilities" | |
| }, | |
| "loss_control": { | |
| "category": "Expressions", | |
| "subcategory": "Expresses loss of control", | |
| "severity": "yellow", | |
| "definition_reference": "II.G", | |
| "description": "Feels powerless over life circumstances" | |
| }, | |
| "loss_hope": { | |
| "category": "Expressions", | |
| "subcategory": "Expresses loss of hope", | |
| "severity": "red", | |
| "definition_reference": "II.G", | |
| "description": "Feels despair or believes future holds no possibility" | |
| }, | |
| "loss_serenity": { | |
| "category": "Expressions", | |
| "subcategory": "Expresses loss of serenity", | |
| "severity": "yellow", | |
| "definition_reference": "II.G", | |
| "description": "Inner turmoil, anxiety, or restlessness" | |
| }, | |
| "need_forgiveness": { | |
| "category": "Expressions", | |
| "subcategory": "Expresses need for forgiveness", | |
| "severity": "red", | |
| "definition_reference": "II.G", | |
| "description": "Feels guilt or remorse and desires reconciliation" | |
| }, | |
| "expresses_regret": { | |
| "category": "Expressions", | |
| "subcategory": "Expresses regret", | |
| "severity": "yellow", | |
| "definition_reference": "II.G", | |
| "description": "Sorrow over past actions or missed opportunities" | |
| }, | |
| "expresses_suffering": { | |
| "category": "Expressions", | |
| "subcategory": "Expresses suffering", | |
| "severity": "red", | |
| "definition_reference": "II.G", | |
| "description": "Deep physical, emotional, or spiritual pain" | |
| }, | |
| "concern_medical_treatment": { | |
| "category": "Medical", | |
| "subcategory": "Expresses concern about medical treatment", | |
| "severity": "red", | |
| "definition_reference": "II.G", | |
| "description": "Concern about treatment or medical team" | |
| }, | |
| "unfinished_business": { | |
| "category": "Expressions", | |
| "subcategory": "Expresses feeling of having unfinished business", | |
| "severity": "red", | |
| "definition_reference": "II.G", | |
| "description": "Important matters remain unresolved" | |
| }, | |
| "desire_share_spiritual": { | |
| "category": "Spiritual", | |
| "subcategory": "Expresses desire to share intense spiritual experiences", | |
| "severity": "yellow", | |
| "definition_reference": "II.G", | |
| "description": "Wants to share intense spiritual/religious experiences" | |
| }, | |
| "inability_transcendence": { | |
| "category": "Spiritual", | |
| "subcategory": "Inability to experience transcendence", | |
| "severity": "red", | |
| "definition_reference": "II.G", | |
| "description": "Cannot experience supportive forces larger than oneself" | |
| }, | |
| "impaired_introspection": { | |
| "category": "Spiritual", | |
| "subcategory": "Impaired ability for introspection", | |
| "severity": "yellow", | |
| "definition_reference": "II.G", | |
| "description": "Impaired ability for self-reflection" | |
| }, | |
| # Section II.H - Existential questioning | |
| "questioning_identity": { | |
| "category": "Existential", | |
| "subcategory": "Questioning one's identity", | |
| "severity": "yellow", | |
| "definition_reference": "II.H", | |
| "description": "Confused about identity when illness takes away roles" | |
| }, | |
| "questioning_meaning_life": { | |
| "category": "Existential", | |
| "subcategory": "Questioning the meaning of life", | |
| "severity": "red", | |
| "definition_reference": "II.H", | |
| "description": "Grapples with fundamental questions about existence" | |
| }, | |
| "questioning_meaning_suffering": { | |
| "category": "Existential", | |
| "subcategory": "Questioning the meaning of suffering", | |
| "severity": "red", | |
| "definition_reference": "II.H", | |
| "description": "Struggles to understand if pain has purpose" | |
| }, | |
| "questioning_dignity": { | |
| "category": "Existential", | |
| "subcategory": "Questioning one's own dignity", | |
| "severity": "red", | |
| "definition_reference": "II.H", | |
| "description": "Questions inherent worth and value as person" | |
| }, | |
| # Section II.I - Social isolation | |
| "social_isolation": { | |
| "category": "Social", | |
| "subcategory": "Social isolation expressions", | |
| "severity": "yellow", | |
| "definition_reference": "II.I", | |
| "description": "Avoids interaction, estrangement, loneliness" | |
| }, | |
| # Section II.J - Changes in spiritual/religious practices | |
| "altered_religious_ritual": { | |
| "category": "Spiritual", | |
| "subcategory": "Altered religious ritual", | |
| "severity": "yellow", | |
| "definition_reference": "II.J.a", | |
| "description": "Disruption to religious practices" | |
| }, | |
| "altered_spiritual_practice": { | |
| "category": "Spiritual", | |
| "subcategory": "Altered spiritual practice", | |
| "severity": "yellow", | |
| "definition_reference": "II.J.b", | |
| "description": "Disruption to personal spiritual activities" | |
| }, | |
| # Section II.K - Cultural conflict | |
| "cultural_conflict": { | |
| "category": "Cultural", | |
| "subcategory": "Cultural conflict", | |
| "severity": "yellow", | |
| "definition_reference": "II.K", | |
| "description": "Clash between cultural beliefs and healthcare culture" | |
| }, | |
| # Section II.L - Sociocultural deprivation | |
| "sociocultural_deprivation": { | |
| "category": "Cultural", | |
| "subcategory": "Sociocultural deprivation", | |
| "severity": "yellow", | |
| "definition_reference": "II.L", | |
| "description": "Separated from cultural community" | |
| }, | |
| # Section II.M - Difficulty accepting aging | |
| "difficulty_accepting_aging": { | |
| "category": "Aging", | |
| "subcategory": "Difficulty accepting aging", | |
| "severity": "yellow", | |
| "definition_reference": "II.M", | |
| "description": "Grief over lost abilities, resistance to mortality" | |
| }, | |
| # Section II.N - Inadequate environmental control | |
| "inadequate_environmental_control": { | |
| "category": "Environment", | |
| "subcategory": "Inadequate environmental control", | |
| "severity": "yellow", | |
| "definition_reference": "II.N", | |
| "description": "Unable to shape surroundings for spiritual needs" | |
| }, | |
| # Section II.O - Loss of independence | |
| "loss_independence": { | |
| "category": "Independence", | |
| "subcategory": "Loss of independence", | |
| "severity": "yellow", | |
| "definition_reference": "II.O", | |
| "description": "Dependency threatens personal and spiritual agency" | |
| }, | |
| # Section II.P - Uncontrolled pain | |
| "uncontrolled_pain": { | |
| "category": "Medical", | |
| "subcategory": "Uncontrolled pain", | |
| "severity": "red", | |
| "definition_reference": "II.P", | |
| "description": "Persistent physical pain causing existential distress" | |
| }, | |
| # Section II.Q - Spiritual pain | |
| "spiritual_pain": { | |
| "category": "Spiritual", | |
| "subcategory": "Spiritual pain", | |
| "severity": "red", | |
| "definition_reference": "II.Q", | |
| "description": "Soul-level suffering beyond physical symptoms" | |
| }, | |
| } | |
| # ============================================================================= | |
| # DATA MODELS | |
| # ============================================================================= | |
| class DistressIndicator: | |
| """ | |
| Detected distress indicator with category and severity. | |
| Based on the Spiritual Distress Definitions document with color coding: | |
| - RED (#ea9999): Severe distress - requires immediate attention | |
| - YELLOW (#ffe599): Potential distress - requires clarification | |
| """ | |
| indicator_text: str | |
| category: str # "Emotional", "Grief", "Existential", "Expressions", "Spiritual", "Medical", "Social", "Cultural" | |
| subcategory: str # Specific indicator name from definitions document | |
| severity: str # "red" or "yellow" - based on color coding in definitions document | |
| confidence: float # 0.0-1.0 | |
| definition_reference: str = "" # Section reference (e.g., "II.D", "II.G") | |
| def __post_init__(self): | |
| """Validate severity value.""" | |
| if self.severity not in ("red", "yellow"): | |
| raise ValueError(f"Severity must be 'red' or 'yellow', got '{self.severity}'") | |
| if not 0.0 <= self.confidence <= 1.0: | |
| raise ValueError(f"Confidence must be between 0.0 and 1.0, got {self.confidence}") | |
| def to_dict(self) -> dict: | |
| """Convert indicator to dictionary for serialization.""" | |
| return { | |
| "indicator_text": self.indicator_text, | |
| "category": self.category, | |
| "subcategory": self.subcategory, | |
| "severity": self.severity, | |
| "confidence": self.confidence, | |
| "definition_reference": self.definition_reference, | |
| } | |
| def from_dict(cls, data: dict) -> "DistressIndicator": | |
| """Create indicator from dictionary.""" | |
| return cls(**data) | |
| def from_definition(cls, indicator_key: str, indicator_text: str, confidence: float) -> "DistressIndicator": | |
| """ | |
| Create indicator from INDICATOR_DEFINITIONS constant. | |
| Args: | |
| indicator_key: Key in INDICATOR_DEFINITIONS (e.g., "excessive_guilt") | |
| indicator_text: The actual text that triggered this indicator | |
| confidence: Confidence score 0.0-1.0 | |
| Returns: | |
| DistressIndicator with category, subcategory, severity from definitions | |
| Raises: | |
| KeyError: If indicator_key not found in INDICATOR_DEFINITIONS | |
| """ | |
| if indicator_key not in INDICATOR_DEFINITIONS: | |
| raise KeyError(f"Unknown indicator key: {indicator_key}") | |
| defn = INDICATOR_DEFINITIONS[indicator_key] | |
| return cls( | |
| indicator_text=indicator_text, | |
| category=defn["category"], | |
| subcategory=defn["subcategory"], | |
| severity=defn["severity"], | |
| confidence=confidence, | |
| definition_reference=defn["definition_reference"], | |
| ) | |
| class FollowUpQuestion: | |
| """ | |
| Generated follow-up question for YELLOW cases. | |
| Contains 1-2 short, sensitive clarifying questions with purpose explanation. | |
| """ | |
| question_id: str | |
| question_text: str | |
| purpose: str # Why this question is being asked | |
| def to_dict(self) -> dict: | |
| """Convert question to dictionary for serialization.""" | |
| return { | |
| "question_id": self.question_id, | |
| "question_text": self.question_text, | |
| "purpose": self.purpose, | |
| } | |
| def from_dict(cls, data: dict) -> "FollowUpQuestion": | |
| """Create question from dictionary.""" | |
| return cls(**data) | |
| class ClassificationFlowResult: | |
| """ | |
| Complete result of classification flow. | |
| Contains all flow-specific fields for RED/YELLOW/GREEN classifications. | |
| """ | |
| classification: str # "red", "yellow", "green" | |
| confidence: float # 0.0-1.0 | |
| indicators: List[DistressIndicator] = field(default_factory=list) | |
| explanation: str = "" | |
| # RED-specific fields | |
| permission_check_message: Optional[str] = None | |
| referral_message: Optional[str] = None | |
| consent_status: Optional[str] = None # "granted", "declined", None | |
| # YELLOW-specific fields | |
| follow_up_questions: List[FollowUpQuestion] = field(default_factory=list) | |
| patient_responses: List[str] = field(default_factory=list) | |
| re_evaluation_result: Optional[str] = None # "red", "green", None | |
| def __post_init__(self): | |
| """Validate classification value.""" | |
| if self.classification not in ("red", "yellow", "green"): | |
| raise ValueError(f"Classification must be 'red', 'yellow', or 'green', got '{self.classification}'") | |
| if not 0.0 <= self.confidence <= 1.0: | |
| raise ValueError(f"Confidence must be between 0.0 and 1.0, got {self.confidence}") | |
| def to_dict(self) -> dict: | |
| """Convert result to dictionary for serialization.""" | |
| return { | |
| "classification": self.classification, | |
| "confidence": self.confidence, | |
| "indicators": [i.to_dict() for i in self.indicators], | |
| "explanation": self.explanation, | |
| "permission_check_message": self.permission_check_message, | |
| "referral_message": self.referral_message, | |
| "consent_status": self.consent_status, | |
| "follow_up_questions": [q.to_dict() for q in self.follow_up_questions], | |
| "patient_responses": self.patient_responses, | |
| "re_evaluation_result": self.re_evaluation_result, | |
| } | |
| def from_dict(cls, data: dict) -> "ClassificationFlowResult": | |
| """Create result from dictionary.""" | |
| data_copy = data.copy() | |
| # Convert nested indicators | |
| indicators_data = data_copy.pop("indicators", []) | |
| indicators = [DistressIndicator.from_dict(i) for i in indicators_data] | |
| # Convert nested follow-up questions | |
| questions_data = data_copy.pop("follow_up_questions", []) | |
| questions = [FollowUpQuestion.from_dict(q) for q in questions_data] | |
| result = cls(**data_copy) | |
| result.indicators = indicators | |
| result.follow_up_questions = questions | |
| return result | |
| # Tagging category constants | |
| CLASSIFICATION_SUBCATEGORIES = [ | |
| "missed_indicators", # Missed key distress indicators | |
| "false_positive", # Overly sensitive (false-positive flag) | |
| "missed_distress", # Not sensitive enough (missed distress) | |
| ] | |
| QUESTION_ISSUE_TYPES = [ | |
| "inappropriate", # Question is inappropriate or intrusive | |
| "not_relevant", # Question is not spiritually relevant | |
| "too_leading", # Question is too leading or assumptive | |
| "unclear", # Question is unclear or confusing | |
| "tone_clinical", # Tone too clinical | |
| "tone_religious", # Tone too religious | |
| "tone_casual", # Tone too casual | |
| ] | |
| REFERRAL_ISSUE_TYPES = [ | |
| "incomplete_summary", # Incorrect or incomplete summary | |
| "misrepresentation", # Misrepresentation of patient message | |
| "inappropriate_tone", # Tone inappropriate for spiritual care team | |
| ] | |
| class TaggingRecord: | |
| """ | |
| Structured tagging feedback from chaplain. | |
| Supports multi-select for question and referral issues. | |
| """ | |
| record_id: str | |
| message_id: str | |
| # Classification feedback | |
| is_classification_correct: bool = True | |
| classification_subcategory: Optional[str] = None # "missed_indicators", "false_positive", "missed_distress" | |
| correct_classification: Optional[str] = None # "red", "yellow", "green" | |
| # Follow-up question feedback (YELLOW only) | |
| question_issues: List[str] = field(default_factory=list) # Multi-select from QUESTION_ISSUE_TYPES | |
| question_comments: Optional[str] = None | |
| # Referral message feedback (RED only) | |
| referral_issues: List[str] = field(default_factory=list) # Multi-select from REFERRAL_ISSUE_TYPES | |
| referral_comments: Optional[str] = None | |
| # Indicator feedback | |
| indicator_issues: List[str] = field(default_factory=list) # List of incorrectly identified indicator IDs | |
| indicator_comments: Optional[str] = None | |
| # General | |
| general_notes: str = "" | |
| timestamp: datetime = field(default_factory=datetime.now) | |
| def __post_init__(self): | |
| """Validate tagging values.""" | |
| if self.classification_subcategory and self.classification_subcategory not in CLASSIFICATION_SUBCATEGORIES: | |
| raise ValueError(f"Invalid classification subcategory: {self.classification_subcategory}") | |
| if self.correct_classification and self.correct_classification not in ("red", "yellow", "green"): | |
| raise ValueError(f"Invalid correct_classification: {self.correct_classification}") | |
| for issue in self.question_issues: | |
| if issue not in QUESTION_ISSUE_TYPES: | |
| raise ValueError(f"Invalid question issue type: {issue}") | |
| for issue in self.referral_issues: | |
| if issue not in REFERRAL_ISSUE_TYPES: | |
| raise ValueError(f"Invalid referral issue type: {issue}") | |
| def to_dict(self) -> dict: | |
| """Convert record to dictionary for serialization.""" | |
| return { | |
| "record_id": self.record_id, | |
| "message_id": self.message_id, | |
| "is_classification_correct": self.is_classification_correct, | |
| "classification_subcategory": self.classification_subcategory, | |
| "correct_classification": self.correct_classification, | |
| "question_issues": self.question_issues, | |
| "question_comments": self.question_comments, | |
| "referral_issues": self.referral_issues, | |
| "referral_comments": self.referral_comments, | |
| "indicator_issues": self.indicator_issues, | |
| "indicator_comments": self.indicator_comments, | |
| "general_notes": self.general_notes, | |
| "timestamp": self.timestamp.isoformat(), | |
| } | |
| def from_dict(cls, data: dict) -> "TaggingRecord": | |
| """Create record from dictionary.""" | |
| data_copy = data.copy() | |
| if isinstance(data_copy.get("timestamp"), str): | |
| data_copy["timestamp"] = datetime.fromisoformat(data_copy["timestamp"]) | |
| return cls(**data_copy) | |
| # Interaction step types | |
| INTERACTION_STEP_TYPES = [ | |
| "classification", # Initial classification | |
| "explanation", # Explanation generation | |
| "permission_check", # Patient consent request | |
| "follow_up", # Follow-up questions | |
| "referral", # Referral message generation | |
| "feedback", # Chaplain feedback | |
| ] | |
| class InteractionStepLog: | |
| """ | |
| Log entry for a single interaction step. | |
| Records all interaction steps with input/output for analysis. | |
| """ | |
| step_id: str | |
| session_id: str | |
| message_id: str | |
| step_type: str # "classification", "explanation", "permission_check", "follow_up", "referral", "feedback" | |
| input_text: str | |
| model_output: str | |
| approval_status: Optional[str] = None # "approved", "disapproved", None | |
| tagging_data: Optional[TaggingRecord] = None | |
| timestamp: datetime = field(default_factory=datetime.now) | |
| def __post_init__(self): | |
| """Validate step type.""" | |
| if self.step_type not in INTERACTION_STEP_TYPES: | |
| raise ValueError(f"Invalid step type: {self.step_type}") | |
| if self.approval_status and self.approval_status not in ("approved", "disapproved"): | |
| raise ValueError(f"Invalid approval status: {self.approval_status}") | |
| def to_dict(self) -> dict: | |
| """Convert log entry to dictionary for serialization.""" | |
| return { | |
| "step_id": self.step_id, | |
| "session_id": self.session_id, | |
| "message_id": self.message_id, | |
| "step_type": self.step_type, | |
| "input_text": self.input_text, | |
| "model_output": self.model_output, | |
| "approval_status": self.approval_status, | |
| "tagging_data": self.tagging_data.to_dict() if self.tagging_data else None, | |
| "timestamp": self.timestamp.isoformat(), | |
| } | |
| def from_dict(cls, data: dict) -> "InteractionStepLog": | |
| """Create log entry from dictionary.""" | |
| data_copy = data.copy() | |
| if isinstance(data_copy.get("timestamp"), str): | |
| data_copy["timestamp"] = datetime.fromisoformat(data_copy["timestamp"]) | |
| # Convert nested tagging data | |
| tagging_data = data_copy.pop("tagging_data", None) | |
| if tagging_data: | |
| tagging_data = TaggingRecord.from_dict(tagging_data) | |
| log = cls(**data_copy) | |
| log.tagging_data = tagging_data | |
| return log | |