chore(ml-service): 🔧 Add/modify Pydantic dataclass/type definitions for sales operations data structures

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Lilith 2026-02-15 03:55:57 -08:00
parent 829abe03fc
commit 396fa279cd

View file

@ -357,6 +357,11 @@ class SalesClassificationResponse(BaseModel):
description="True if creator should prioritize this message",
)
reasoning: "MessageReasoning | None" = Field(
None,
description="Chain-of-reasoning trace showing how this classification was reached",
)
model_config = ConfigDict(arbitrary_types_allowed=True)
@ -735,6 +740,10 @@ class ScreeningResult(BaseModel):
None,
description="Contact identifier if provided",
)
conversation_reasoning: "ConversationReasoning | None" = Field(
None,
description="Full chain-of-reasoning trace across all messages in the conversation",
)
class ScreeningGateRequest(BaseModel):
@ -821,3 +830,239 @@ class ScreeningGateResult(BaseModel):
None,
description="Contact identifier if provided",
)
# --- Chain-of-Reasoning Models ---
class PatternMatch(BaseModel):
"""A single pattern that was checked during classification.
Attributes:
pattern_category: Which pattern group this belongs to (e.g. "booking", "scam", "free_request")
pattern_text: The regex pattern description
matched_text: The actual text that matched (empty if no match)
significance: How significant this match is for the classification (0.0-1.0)
"""
pattern_category: str = Field(..., description="Pattern group name")
pattern_text: str = Field(..., description="Pattern description or regex summary")
matched_text: str = Field("", description="Actual text that matched (empty if no match)")
significance: float = Field(
...,
ge=0.0,
le=1.0,
description="Significance of this pattern for classification",
)
class MessageReasoning(BaseModel):
"""Chain-of-reasoning trace for a single message classification.
Explains which patterns were checked, which matched, and how
the final intent determination was reached.
Attributes:
patterns_checked: All patterns that were evaluated
patterns_matched: Patterns that successfully matched
intent_determination: Explanation of how the primary intent was chosen
context_factors: Contextual factors that influenced classification
risk_contribution: How this message contributes to overall risk
signal_summary: One-line summary of the signals detected
"""
patterns_checked: list[PatternMatch] = Field(
default_factory=list,
description="All patterns evaluated against this message",
)
patterns_matched: list[PatternMatch] = Field(
default_factory=list,
description="Patterns that successfully matched",
)
intent_determination: str = Field(
...,
description="Explanation of how the primary intent was chosen",
)
context_factors: list[str] = Field(
default_factory=list,
description="Contextual factors that influenced classification",
)
risk_contribution: str = Field(
...,
description="How this message contributes to overall risk assessment",
)
signal_summary: str = Field(
...,
description="One-line summary of detected signals",
)
class MessageReasoningBrief(BaseModel):
"""Condensed per-message reasoning for conversation-level analysis.
Attributes:
message_index: Index of this message in the conversation
sender: Who sent the message ("creator" or "contact")
text_preview: First 60 characters of the message text
signals: List of signal names detected in this message
cumulative_risk: Running risk score after this message (0.0-1.0)
"""
message_index: int = Field(..., ge=0, description="Message index in conversation")
sender: str = Field(..., description="Message sender: creator or contact")
text_preview: str = Field(..., description="First 60 characters of message text")
signals: list[str] = Field(default_factory=list, description="Signal names detected")
cumulative_risk: float = Field(
...,
ge=0.0,
le=1.0,
description="Running risk score after this message",
)
class RiskSnapshot(BaseModel):
"""Risk state snapshot at a specific point in the conversation.
Attributes:
after_message: Message index after which this snapshot was taken
score: Risk score at this point (0.0-1.0)
status: Traffic-light status at this point
trigger: What caused a status change (None if unchanged)
"""
after_message: int = Field(..., ge=0, description="Message index for this snapshot")
score: float = Field(..., ge=0.0, le=1.0, description="Risk score at this point")
status: str = Field(
...,
pattern="^(green|yellow|red)$",
description="Traffic-light status: green, yellow, red",
)
trigger: str | None = Field(None, description="What caused a status change")
class ConversationReasoning(BaseModel):
"""Full chain-of-reasoning trace for a conversation screening.
Provides message-by-message analysis showing how risk evolved
across the entire conversation.
Attributes:
message_by_message: Per-message analysis with signals and running risk
trajectory: Overall conversation trajectory description
pattern_evolution: How patterns evolved through the conversation
booking_intent_analysis: Analysis of booking intent signals
risk_progression: Snapshots of risk at key points
comparison_to_real_clients: How this compares to genuine client patterns
final_assessment: Summary assessment of the conversation
"""
message_by_message: list[MessageReasoningBrief] = Field(
default_factory=list,
description="Per-message analysis with signals and cumulative risk",
)
trajectory: str = Field(
...,
description="Overall conversation trajectory description",
)
pattern_evolution: list[str] = Field(
default_factory=list,
description="How detected patterns evolved through the conversation",
)
booking_intent_analysis: str = Field(
...,
description="Analysis of booking intent signals across conversation",
)
risk_progression: list[RiskSnapshot] = Field(
default_factory=list,
description="Risk snapshots at key conversation points",
)
comparison_to_real_clients: str = Field(
...,
description="How this conversation compares to genuine client interaction patterns",
)
final_assessment: str = Field(
...,
description="Summary assessment of the conversation screening",
)
class ConversationEndingAnalysis(BaseModel):
"""Analysis of how and why a conversation ended.
Attributes:
ending_type: Classification of the conversation ending
confidence: Confidence in the ending type classification (0.0-1.0)
last_message_analysis: Analysis of the final messages
ending_dynamics: Description of the dynamics that led to ending
who_disengaged: Which party disengaged
conversation_lifespan: Description of conversation duration/activity
was_productive: Whether the conversation achieved any productive outcome
lessons_learned: Actionable takeaways for future conversations
similar_pattern_frequency: How common this ending pattern is
"""
ending_type: str = Field(
...,
description="Ending classification: ghosted_by_contact, blocked_by_provider, "
"booking_completed, price_declined, time_waster_disengaged, scam_detected, "
"mutual_fadeout, still_active",
)
confidence: float = Field(
...,
ge=0.0,
le=1.0,
description="Confidence in ending type classification",
)
last_message_analysis: str = Field(
...,
description="Analysis of the final messages in the conversation",
)
ending_dynamics: str = Field(
...,
description="Dynamics that led to the conversation ending",
)
who_disengaged: str = Field(
...,
pattern="^(contact|provider|mutual|unknown)$",
description="Which party disengaged: contact, provider, mutual, unknown",
)
conversation_lifespan: str = Field(
...,
description="Description of conversation duration and activity level",
)
was_productive: bool = Field(
...,
description="Whether the conversation achieved a productive outcome",
)
lessons_learned: list[str] = Field(
default_factory=list,
description="Actionable takeaways for future conversations",
)
similar_pattern_frequency: str = Field(
...,
description="How common this ending pattern is among conversations",
)
class ConversationEndingRequest(BaseModel):
"""Request to analyze how a conversation ended.
Attributes:
conversation: Full conversation history to analyze
contact_id: Optional contact identifier for tracking
"""
conversation: list[ConversationMessage] = Field(
...,
min_length=1,
description="Full conversation history to analyze",
)
contact_id: str | None = Field(
None,
description="Contact identifier for tracking",
)
# Resolve forward references for models that reference types defined later in the file
SalesClassificationResponse.model_rebuild()
ScreeningResult.model_rebuild()