Spaces:
Sleeping
Sleeping
File size: 28,087 Bytes
ab93d81 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 |
# 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
# =============================================================================
@dataclass
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,
}
@classmethod
def from_dict(cls, data: dict) -> "DistressIndicator":
"""Create indicator from dictionary."""
return cls(**data)
@classmethod
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"],
)
@dataclass
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,
}
@classmethod
def from_dict(cls, data: dict) -> "FollowUpQuestion":
"""Create question from dictionary."""
return cls(**data)
@dataclass
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,
}
@classmethod
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
]
@dataclass
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(),
}
@classmethod
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
]
@dataclass
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(),
}
@classmethod
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
|