"""MMA (Multi-Model Architecture) core data structures. Per module_taxonomy_refactor_20260627 Phase 3.1, the MMA Core (ThinkingSegment, Ticket, Track, WorkerContext, TrackMetadata, TrackState) moved from src/models.py to this module. The data domain is the ticket/track lifecycle that drives the 4-Tier MMA execution. The boundary wire schema `Metadata` (TypeAlias = dict[str, Any]) is NOT defined here; it lives in src/type_aliases.py. This module's TrackMetadata dataclass is the *typed* counterpart used for Track-level metadata (id/name/status/timestamps). """ from __future__ import annotations import datetime from dataclasses import dataclass, field from typing import List, Optional from src.type_aliases import Metadata @dataclass class ThinkingSegment: content: str marker: str def to_dict(self) -> Metadata: return {"content": self.content, "marker": self.marker} @classmethod def from_dict(cls, data: Metadata) -> "ThinkingSegment": return cls(content=data["content"], marker=data["marker"]) @dataclass class Ticket: id: str description: str target_symbols: List[str] = field(default_factory=list) context_requirements: List[str] = field(default_factory=list) depends_on: List[str] = field(default_factory=list) status: str = "todo" assigned_to: str = "unassigned" priority: str = "medium" target_file: Optional[str] = None blocked_reason: Optional[str] = None step_mode: bool = False retry_count: int = 0 manual_block: bool = False model_override: Optional[str] = None persona_id: Optional[str] = None def mark_blocked(self, reason: str) -> None: self.status = "blocked" self.blocked_reason = reason def mark_manual_block(self, reason: str) -> None: self.status = "blocked" self.blocked_reason = f"[MANUAL] {reason}" self.manual_block = True def clear_manual_block(self) -> None: if self.manual_block: self.status = "todo" self.blocked_reason = None self.manual_block = False def mark_complete(self) -> None: self.status = "completed" def to_dict(self) -> Metadata: return { "id": self.id, "description": self.description, "status": self.status, "assigned_to": self.assigned_to, "priority": self.priority, "target_file": self.target_file, "target_symbols": self.target_symbols, "context_requirements": self.context_requirements, "depends_on": self.depends_on, "blocked_reason": self.blocked_reason, "step_mode": self.step_mode, "retry_count": self.retry_count, "manual_block": self.manual_block, "model_override": self.model_override, "persona_id": self.persona_id, } @classmethod def from_dict(cls, data: Metadata) -> "Ticket": return cls( id = data["id"], description = data.get("description", ""), status = data.get("status", "todo"), assigned_to = data.get("assigned_to", "unassigned"), priority = data.get("priority", "medium"), target_file = data.get("target_file"), target_symbols = data.get("target_symbols", []), context_requirements = data.get("context_requirements", []), depends_on = data.get("depends_on", []), blocked_reason = data.get("blocked_reason"), step_mode = data.get("step_mode", False), retry_count = data.get("retry_count", 0), manual_block = data.get("manual_block", False), model_override = data.get("model_override"), persona_id = data.get("persona_id"), ) @dataclass class Track: id: str description: str tickets: List["Ticket"] = field(default_factory=list) def to_dict(self) -> Metadata: return { "id": self.id, "description": self.description, "tickets": [t.to_dict() for t in self.tickets], } @classmethod def from_dict(cls, data: Metadata) -> "Track": return cls( id = data["id"], description = data.get("description", ""), tickets = [Ticket.from_dict(t) for t in data.get("tickets", [])], ) @dataclass class WorkerContext: ticket_id: str model_name: str messages: list[Metadata] = field(default_factory=list) tool_preset: Optional[str] = None persona_id: Optional[str] = None @dataclass class TrackMetadata: id: str name: str status: Optional[str] = None created_at: Optional[datetime.datetime] = None updated_at: Optional[datetime.datetime] = None def to_dict(self) -> Metadata: return { "id": self.id, "name": self.name, "status": self.status, "created_at": self.created_at.isoformat() if self.created_at else None, "updated_at": self.updated_at.isoformat() if self.updated_at else None, } @classmethod def from_dict(cls, data: Metadata) -> "TrackMetadata": created = data.get("created_at") updated = data.get("updated_at") if isinstance(created, str): try: created = datetime.datetime.fromisoformat(created) except ValueError: created = None if isinstance(updated, str): try: updated = datetime.datetime.fromisoformat(updated) except ValueError: updated = None return cls( id = data["id"], name = data.get("name", ""), status = data.get("status"), created_at = created, updated_at = updated, ) @dataclass class TrackState: metadata: Metadata = field(default_factory=dict) discussion: List[Metadata] = field(default_factory=list) tasks: List["Ticket"] = field(default_factory=list) def to_dict(self) -> Metadata: serialized_discussion = [] for item in self.discussion: if isinstance(item, dict): new_item = dict(item) if "ts" in new_item and isinstance(new_item["ts"], datetime.datetime): new_item["ts"] = new_item["ts"].isoformat() serialized_discussion.append(new_item) else: serialized_discussion.append(item) return { "metadata": self.metadata.to_dict(), "discussion": serialized_discussion, "tasks": [t.to_dict() for t in self.tasks], } @classmethod def from_dict(cls, data: Metadata) -> "TrackState": discussion = data.get("discussion", []) parsed_discussion = [] for item in discussion: if isinstance(item, dict): new_item = dict(item) ts = new_item.get("ts") if isinstance(ts, str): try: new_item["ts"] = datetime.datetime.fromisoformat(ts) except ValueError: pass parsed_discussion.append(new_item) else: parsed_discussion.append(item) return cls( metadata = TrackMetadata.from_dict(data["metadata"]), discussion = parsed_discussion, tasks = [Ticket.from_dict(t) for t in data.get("tasks", [])], ) EMPTY_TRACK_STATE: TrackState = TrackState()