gestura_core_foundation/
outcomes.rs1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use std::fmt;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
7#[serde(rename_all = "snake_case")]
8pub enum OutcomeSignalKind {
9 RetryImproved,
11 RetryDidNotImprove,
13 ExecutionAwaitingReview,
15 ExecutionAwaitingTestValidation,
17 TaskCompleted,
19 TaskFailed,
21 TaskBlocked,
23 TaskCancelled,
25 PreExecutionApproved,
27 PreExecutionRejected,
29 PreExecutionNeedsRevision,
31 ReviewApproved,
33 ReviewRejected,
35 ReviewNeedsRevision,
37 TestValidationApproved,
39 TestValidationRejected,
41 TestValidationNeedsRevision,
43}
44
45impl OutcomeSignalKind {
46 #[must_use]
48 pub const fn as_str(self) -> &'static str {
49 match self {
50 Self::RetryImproved => "retry_improved",
51 Self::RetryDidNotImprove => "retry_did_not_improve",
52 Self::ExecutionAwaitingReview => "execution_awaiting_review",
53 Self::ExecutionAwaitingTestValidation => "execution_awaiting_test_validation",
54 Self::TaskCompleted => "task_completed",
55 Self::TaskFailed => "task_failed",
56 Self::TaskBlocked => "task_blocked",
57 Self::TaskCancelled => "task_cancelled",
58 Self::PreExecutionApproved => "pre_execution_approved",
59 Self::PreExecutionRejected => "pre_execution_rejected",
60 Self::PreExecutionNeedsRevision => "pre_execution_needs_revision",
61 Self::ReviewApproved => "review_approved",
62 Self::ReviewRejected => "review_rejected",
63 Self::ReviewNeedsRevision => "review_needs_revision",
64 Self::TestValidationApproved => "test_validation_approved",
65 Self::TestValidationRejected => "test_validation_rejected",
66 Self::TestValidationNeedsRevision => "test_validation_needs_revision",
67 }
68 }
69
70 #[must_use]
72 pub const fn label(self) -> &'static str {
73 match self {
74 Self::RetryImproved => "Retry improved",
75 Self::RetryDidNotImprove => "Retry did not improve",
76 Self::ExecutionAwaitingReview => "Execution awaiting review",
77 Self::ExecutionAwaitingTestValidation => "Execution awaiting test validation",
78 Self::TaskCompleted => "Task completed",
79 Self::TaskFailed => "Task failed",
80 Self::TaskBlocked => "Task blocked",
81 Self::TaskCancelled => "Task cancelled",
82 Self::PreExecutionApproved => "Pre-execution approved",
83 Self::PreExecutionRejected => "Pre-execution rejected",
84 Self::PreExecutionNeedsRevision => "Pre-execution needs revision",
85 Self::ReviewApproved => "Review approved",
86 Self::ReviewRejected => "Review rejected",
87 Self::ReviewNeedsRevision => "Review needs revision",
88 Self::TestValidationApproved => "Test validation approved",
89 Self::TestValidationRejected => "Test validation rejected",
90 Self::TestValidationNeedsRevision => "Test validation needs revision",
91 }
92 }
93
94 #[must_use]
96 pub const fn confidence_delta(self) -> f32 {
97 match self {
98 Self::RetryImproved => 0.03,
99 Self::RetryDidNotImprove => -0.10,
100 Self::ExecutionAwaitingReview | Self::ExecutionAwaitingTestValidation => 0.02,
101 Self::TaskCompleted => 0.12,
102 Self::TaskFailed | Self::TaskBlocked => -0.12,
103 Self::TaskCancelled => -0.08,
104 Self::PreExecutionApproved | Self::ReviewApproved | Self::TestValidationApproved => {
105 0.08
106 }
107 Self::PreExecutionNeedsRevision
108 | Self::ReviewNeedsRevision
109 | Self::TestValidationNeedsRevision => -0.12,
110 Self::PreExecutionRejected | Self::ReviewRejected | Self::TestValidationRejected => {
111 -0.16
112 }
113 }
114 }
115}
116
117impl fmt::Display for OutcomeSignalKind {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 f.write_str(self.label())
120 }
121}
122
123#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
125pub struct OutcomeSignal {
126 pub kind: OutcomeSignalKind,
128 pub observed_at: DateTime<Utc>,
130 #[serde(default, skip_serializing_if = "Option::is_none")]
132 pub summary: Option<String>,
133}
134
135impl OutcomeSignal {
136 #[must_use]
138 pub fn new(kind: OutcomeSignalKind) -> Self {
139 Self {
140 kind,
141 observed_at: Utc::now(),
142 summary: None,
143 }
144 }
145
146 #[must_use]
148 pub fn with_summary(mut self, summary: impl Into<String>) -> Self {
149 self.summary = Some(summary.into());
150 self
151 }
152
153 #[must_use]
155 pub const fn durable_label(&self) -> &'static str {
156 self.kind.as_str()
157 }
158}