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

1"""BatchMaterializeResult value object for bulk materialization operations.""" 

2 

3from dataclasses import dataclass 

4from typing import TYPE_CHECKING 

5 

6if TYPE_CHECKING: 

7 from prosemark.domain.materialize_failure import MaterializeFailure 

8 from prosemark.domain.materialize_result import MaterializeResult 

9 

10 

11@dataclass(frozen=True) 

12class BatchMaterializeResult: 

13 """Represents the outcome of materializing all placeholders in a binder. 

14 

15 This value object encapsulates the complete result of a batch materialization 

16 operation, including both successes and failures, along with execution metrics. 

17 

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 

23 

24 Raises: 

25 ValueError: If validation rules are violated during construction 

26 

27 """ 

28 

29 total_placeholders: int 

30 successful_materializations: list['MaterializeResult'] 

31 failed_materializations: list['MaterializeFailure'] 

32 execution_time: float 

33 

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) 

45 

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) 

50 

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 

55 

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 

62 

63 @property 

64 def has_failures(self) -> bool: 

65 """Check if any materializations failed.""" 

66 return len(self.failed_materializations) > 0 

67 

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 ) 

76 

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 ) 

85 

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' 

90 

91 if self.is_complete_success: 

92 return f'Successfully materialized all {self.total_placeholders} placeholders' 

93 

94 if self.is_complete_failure: 

95 return f'Failed to materialize all {self.total_placeholders} placeholders' 

96 

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)'