Source code for apala_client.models

# Copyright (c) 2025 Apala Cap. All rights reserved.
# This software is proprietary and confidential.

"""
Data models for Apala API client.

All models use Pydantic for type safety and validation.
"""

import uuid
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional

from pydantic import BaseModel, Field, field_validator


# API Response Types
[docs] class AuthResponse(BaseModel): """Authentication response from the server.""" access_token: str refresh_token: str token_type: str expires_in: int company_id: str company_name: str
[docs] class RefreshResponse(BaseModel): """Token refresh response from the server.""" access_token: str expires_in: int
[docs] class CandidateMessageResponse(BaseModel): """Candidate message in processing response.""" content: str channel: str message_id: str
[docs] class MessageProcessingResponse(BaseModel): """Message processing response from the server.""" company: str customer_id: str candidate_message: CandidateMessageResponse
[docs] class MessageOptimizationResponse(BaseModel): """Message optimization response from the server.""" message_id: str optimized_message: str recommended_channel: str original_message: str
[docs] class FeedbackResponse(BaseModel): """Feedback submission response from the server.""" id: str message_id: str customer_responded: bool score: int actual_sent_message: Optional[str] = None inserted_at: str
[docs] class FeedbackItemResponse(BaseModel): """Individual feedback item in bulk response.""" id: str message_id: str customer_responded: bool score: int actual_sent_message: Optional[str] = None inserted_at: str
[docs] class BulkFeedbackResponse(BaseModel): """Bulk feedback submission response from the server.""" success: bool count: int feedback: List[FeedbackItemResponse]
[docs] class Message(BaseModel): """Represents a customer message.""" content: str channel: str = Field(..., description="Channel type: SMS, EMAIL, or OTHER") message_id: Optional[str] = None send_timestamp: Optional[str] = None reply_or_not: bool = False
[docs] @field_validator("channel") @classmethod def validate_channel(cls, v: str) -> str: """Validate channel is one of the allowed values.""" valid_channels = {"SMS", "EMAIL", "OTHER"} if v not in valid_channels: raise ValueError(f"Channel must be one of: {valid_channels}") return v
[docs] def model_post_init(self, __context: Any) -> None: """Generate message_id and timestamp if not provided.""" if self.message_id is None: self.message_id = uuid.uuid4().hex if self.send_timestamp is None: self.send_timestamp = datetime.now(timezone.utc).isoformat()
[docs] def to_dict(self) -> Dict[str, Any]: """Convert to dictionary for API requests.""" return { "content": self.content, "message_id": self.message_id, "channel": self.channel, "send_timestamp": self.send_timestamp, "reply_or_not": "true" if self.reply_or_not else "false", }
[docs] class MessageFeedback(BaseModel): """Represents feedback for a processed message.""" original_message_id: str sent_message_content: str customer_responded: bool quality_score: int = Field(..., ge=0, le=100, description="Quality score from 0-100") time_to_respond_ms: Optional[int] = Field(None, ge=0)
[docs] def to_dict(self) -> Dict[str, Any]: """Convert to dictionary for API requests.""" data = { "original_message_id": self.original_message_id, "sent_message_content": self.sent_message_content, "customer_responded": self.customer_responded, "quality_score": self.quality_score, } if self.time_to_respond_ms is not None: data["time_to_respond_in_millis"] = self.time_to_respond_ms return data
[docs] class MessageHistory(BaseModel): """Represents a collection of customer messages and a candidate response.""" messages: List[Message] candidate_message: Message customer_id: str = Field(..., description="Customer UUID") zip_code: str = Field(..., pattern=r"^\d{5}$", description="5-digit zip code") company_guid: str = Field(..., description="Company UUID")
[docs] @field_validator("customer_id", "company_guid") @classmethod def validate_uuid(cls, v: str) -> str: """Validate UUID format.""" try: uuid.UUID(v) except ValueError: raise ValueError(f"Invalid UUID format: {v}") return v
[docs] def to_processing_dict(self) -> Dict[str, Any]: """Convert to dictionary for message processing API requests.""" return { "company": self.company_guid, "customer_id": self.customer_id, "zip_code": self.zip_code, "messages": [msg.to_dict() for msg in self.messages], "candidate_message": self.candidate_message.to_dict(), }
[docs] def to_optimization_dict(self) -> Dict[str, Any]: """Convert to dictionary for message optimization API requests.""" return { "company": self.company_guid, "customer_id": self.customer_id, "zip_code": self.zip_code, "messages": [msg.to_dict() for msg in self.messages], "candidate_message": self.candidate_message.content, # Just content for optimization }