feat(mma): Implement state mutator methods for Ticket and Track
This commit is contained in:
37
models.py
37
models.py
@@ -1,5 +1,5 @@
|
|||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import List
|
from typing import List, Optional
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Ticket:
|
class Ticket:
|
||||||
@@ -11,6 +11,16 @@ class Ticket:
|
|||||||
status: str
|
status: str
|
||||||
assigned_to: str
|
assigned_to: str
|
||||||
depends_on: List[str] = field(default_factory=list)
|
depends_on: List[str] = field(default_factory=list)
|
||||||
|
blocked_reason: Optional[str] = None
|
||||||
|
|
||||||
|
def mark_blocked(self, reason: str):
|
||||||
|
"""Sets the ticket status to 'blocked' and records the reason."""
|
||||||
|
self.status = "blocked"
|
||||||
|
self.blocked_reason = reason
|
||||||
|
|
||||||
|
def mark_complete(self):
|
||||||
|
"""Sets the ticket status to 'completed'."""
|
||||||
|
self.status = "completed"
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Track:
|
class Track:
|
||||||
@@ -21,6 +31,31 @@ class Track:
|
|||||||
description: str
|
description: str
|
||||||
tickets: List[Ticket] = field(default_factory=list)
|
tickets: List[Ticket] = field(default_factory=list)
|
||||||
|
|
||||||
|
def get_executable_tickets(self) -> List[Ticket]:
|
||||||
|
"""
|
||||||
|
Returns all 'todo' tickets whose dependencies are all 'completed'.
|
||||||
|
"""
|
||||||
|
# Map ticket IDs to their current status for efficient lookup
|
||||||
|
status_map = {t.id: t.status for t in self.tickets}
|
||||||
|
|
||||||
|
executable = []
|
||||||
|
for ticket in self.tickets:
|
||||||
|
if ticket.status != "todo":
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check if all dependencies are completed
|
||||||
|
all_deps_completed = True
|
||||||
|
for dep_id in ticket.depends_on:
|
||||||
|
# If a dependency is missing from the track, we treat it as not completed (or we could raise an error)
|
||||||
|
if status_map.get(dep_id) != "completed":
|
||||||
|
all_deps_completed = False
|
||||||
|
break
|
||||||
|
|
||||||
|
if all_deps_completed:
|
||||||
|
executable.append(ticket)
|
||||||
|
|
||||||
|
return executable
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class WorkerContext:
|
class WorkerContext:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -89,3 +89,88 @@ def test_worker_context_instantiation():
|
|||||||
assert context.ticket_id == ticket_id
|
assert context.ticket_id == ticket_id
|
||||||
assert context.model_name == model_name
|
assert context.model_name == model_name
|
||||||
assert context.messages == messages
|
assert context.messages == messages
|
||||||
|
|
||||||
|
def test_ticket_mark_blocked():
|
||||||
|
"""
|
||||||
|
Verifies that ticket.mark_blocked(reason) sets the status to 'blocked'.
|
||||||
|
Note: The reason field might need to be added to the Ticket class.
|
||||||
|
"""
|
||||||
|
ticket = Ticket(id="T1", description="Task 1", status="todo", assigned_to="a")
|
||||||
|
ticket.mark_blocked("Waiting for API key")
|
||||||
|
assert ticket.status == "blocked"
|
||||||
|
|
||||||
|
def test_ticket_mark_complete():
|
||||||
|
"""
|
||||||
|
Verifies that ticket.mark_complete() sets the status to 'completed'.
|
||||||
|
"""
|
||||||
|
ticket = Ticket(id="T1", description="Task 1", status="todo", assigned_to="a")
|
||||||
|
ticket.mark_complete()
|
||||||
|
assert ticket.status == "completed"
|
||||||
|
|
||||||
|
def test_track_get_executable_tickets():
|
||||||
|
"""
|
||||||
|
Verifies that track.get_executable_tickets() returns only 'todo' tickets
|
||||||
|
whose dependencies are all 'completed'.
|
||||||
|
"""
|
||||||
|
# T1: todo, no deps -> executable
|
||||||
|
t1 = Ticket(id="T1", description="T1", status="todo", assigned_to="a")
|
||||||
|
# T2: todo, deps [T1] -> not executable (T1 is todo)
|
||||||
|
t2 = Ticket(id="T2", description="T2", status="todo", assigned_to="a", depends_on=["T1"])
|
||||||
|
# T3: todo, deps [T4] -> not executable (T4 is blocked)
|
||||||
|
t3 = Ticket(id="T3", description="T3", status="todo", assigned_to="a", depends_on=["T4"])
|
||||||
|
# T4: blocked, no deps -> not executable (not 'todo')
|
||||||
|
t4 = Ticket(id="T4", description="T4", status="blocked", assigned_to="a")
|
||||||
|
# T5: completed, no deps -> not executable (not 'todo')
|
||||||
|
t5 = Ticket(id="T5", description="T5", status="completed", assigned_to="a")
|
||||||
|
# T6: todo, deps [T5] -> executable (T5 is completed)
|
||||||
|
t6 = Ticket(id="T6", description="T6", status="todo", assigned_to="a", depends_on=["T5"])
|
||||||
|
|
||||||
|
track = Track(id="TR1", description="Track 1", tickets=[t1, t2, t3, t4, t5, t6])
|
||||||
|
|
||||||
|
executable = track.get_executable_tickets()
|
||||||
|
executable_ids = [t.id for t in executable]
|
||||||
|
|
||||||
|
assert "T1" in executable_ids
|
||||||
|
assert "T6" in executable_ids
|
||||||
|
assert len(executable_ids) == 2
|
||||||
|
|
||||||
|
def test_track_get_executable_tickets_complex():
|
||||||
|
"""
|
||||||
|
Verifies executable tickets with complex dependency chains.
|
||||||
|
Chain: T1 (comp) -> T2 (todo) -> T3 (todo)
|
||||||
|
T4 (comp) -> T3
|
||||||
|
T5 (todo) -> T3
|
||||||
|
"""
|
||||||
|
t1 = Ticket(id="T1", description="T1", status="completed", assigned_to="a")
|
||||||
|
t2 = Ticket(id="T2", description="T2", status="todo", assigned_to="a", depends_on=["T1"])
|
||||||
|
t3 = Ticket(id="T3", description="T3", status="todo", assigned_to="a", depends_on=["T2", "T4", "T5"])
|
||||||
|
t4 = Ticket(id="T4", description="T4", status="completed", assigned_to="a")
|
||||||
|
t5 = Ticket(id="T5", description="T5", status="todo", assigned_to="a")
|
||||||
|
|
||||||
|
track = Track(id="TR1", description="Track 1", tickets=[t1, t2, t3, t4, t5])
|
||||||
|
|
||||||
|
# At this point:
|
||||||
|
# T1 is completed
|
||||||
|
# T4 is completed
|
||||||
|
# T2 is todo, depends on T1 (completed) -> Executable
|
||||||
|
# T5 is todo, no deps -> Executable
|
||||||
|
# T3 is todo, depends on T2 (todo), T4 (completed), T5 (todo) -> Not executable
|
||||||
|
|
||||||
|
executable = track.get_executable_tickets()
|
||||||
|
executable_ids = sorted([t.id for t in executable])
|
||||||
|
|
||||||
|
assert executable_ids == ["T2", "T5"]
|
||||||
|
|
||||||
|
# Mark T2 complete
|
||||||
|
t2.mark_complete()
|
||||||
|
# T3 still depends on T5
|
||||||
|
executable = track.get_executable_tickets()
|
||||||
|
executable_ids = sorted([t.id for t in executable])
|
||||||
|
assert executable_ids == ["T5"]
|
||||||
|
|
||||||
|
# Mark T5 complete
|
||||||
|
t5.mark_complete()
|
||||||
|
# Now T3 should be executable
|
||||||
|
executable = track.get_executable_tickets()
|
||||||
|
executable_ids = sorted([t.id for t in executable])
|
||||||
|
assert executable_ids == ["T3"]
|
||||||
|
|||||||
Reference in New Issue
Block a user