Coverage for src/prosemark/domain/batch_materialize_result.py: 100%
43 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-09-24 18:08 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-09-24 18:08 +0000
1"""BatchMaterializeResult value object for bulk materialization operations."""
3from dataclasses import dataclass
4from typing import TYPE_CHECKING
6if TYPE_CHECKING:
7 from prosemark.domain.materialize_failure import MaterializeFailure
8 from prosemark.domain.materialize_result import MaterializeResult
11@dataclass(frozen=True)
12class BatchMaterializeResult:
13 """Represents the outcome of materializing all placeholders in a binder.
15 This value object encapsulates the complete result of a batch materialization
16 operation, including both successes and failures, along with execution metrics.
18 Args:
19 total_placeholders: Total number of placeholders found and processed
20 successful_materializations: List of successful materialization results
21 failed_materializations: List of failed materialization attempts
22 execution_time: Total time taken for batch operation in seconds
24 Raises:
25 ValueError: If validation rules are violated during construction
27 """
29 total_placeholders: int
30 successful_materializations: list['MaterializeResult']
31 failed_materializations: list['MaterializeFailure']
32 execution_time: float
34 def __post_init__(self) -> None:
35 """Validate the batch result after construction."""
36 # Validate total_placeholders matches sum of successes and failures
37 actual_total = len(self.successful_materializations) + len(self.failed_materializations)
38 if self.total_placeholders != actual_total:
39 msg = (
40 f'Total placeholders {self.total_placeholders} must equal '
41 f'successes {len(self.successful_materializations)} + '
42 f'failures {len(self.failed_materializations)} = {actual_total}'
43 )
44 raise ValueError(msg)
46 # Validate execution_time is non-negative
47 if self.execution_time < 0:
48 msg = f'Execution time must be non-negative, got {self.execution_time}'
49 raise ValueError(msg)
51 # Validate that if placeholders exist, we have at least some results
52 if self.total_placeholders > 0 and actual_total == 0: # pragma: no cover
53 msg = f'If total_placeholders is {self.total_placeholders}, must have results' # pragma: no cover
54 raise ValueError(msg) # pragma: no cover
56 @property
57 def success_rate(self) -> float:
58 """Calculate the success rate as a percentage."""
59 if self.total_placeholders == 0:
60 return 100.0
61 return (len(self.successful_materializations) / self.total_placeholders) * 100.0
63 @property
64 def has_failures(self) -> bool:
65 """Check if any materializations failed."""
66 return len(self.failed_materializations) > 0
68 @property
69 def is_complete_success(self) -> bool:
70 """Check if all materializations succeeded."""
71 return (
72 self.total_placeholders > 0
73 and len(self.successful_materializations) == self.total_placeholders
74 and not self.has_failures
75 )
77 @property
78 def is_complete_failure(self) -> bool:
79 """Check if all materializations failed."""
80 return (
81 self.total_placeholders > 0
82 and len(self.failed_materializations) == self.total_placeholders
83 and len(self.successful_materializations) == 0
84 )
86 def summary_message(self) -> str:
87 """Generate a human-readable summary message."""
88 if self.total_placeholders == 0:
89 return 'No placeholders found in binder'
91 if self.is_complete_success:
92 return f'Successfully materialized all {self.total_placeholders} placeholders'
94 if self.is_complete_failure:
95 return f'Failed to materialize all {self.total_placeholders} placeholders'
97 # Partial success/failure
98 successful_count = len(self.successful_materializations)
99 failed_count = len(self.failed_materializations)
100 return f'Materialized {successful_count} of {self.total_placeholders} placeholders ({failed_count} failures)'