1use mj_core::review::driver::{Resolution, RoleStatus, TurnReviewPhase};
4use mj_core::review::verdict::ReviewVerdict;
5
6#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
8#[serde(deny_unknown_fields)]
9pub struct RuntimeReviewView {
10 pub session_id: String,
11 pub phase: TurnReviewPhase,
12 pub roles: Vec<RoleStatus>,
13 pub status: String,
15 pub verdict: Option<VerdictView>,
17 #[serde(default, skip_serializing_if = "Vec::is_empty")]
20 pub questions: Vec<ReviewerQuestion>,
21}
22
23#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
25#[serde(deny_unknown_fields)]
26pub struct ReviewerQuestion {
27 pub role: String,
28 pub request: mj_core::elicitation::ElicitationRequest,
29}
30
31const REVIEWER_QUESTION_PREFIX: &str = "review.";
35
36impl ReviewerQuestion {
37 #[must_use]
40 pub fn session_request(&self) -> mj_core::elicitation::ElicitationRequest {
41 let mut request = self.request.clone();
42 request.id = format!(
43 "{REVIEWER_QUESTION_PREFIX}{}.{}",
44 self.role, self.request.id
45 );
46 request
47 }
48}
49
50#[must_use]
53pub fn parse_reviewer_question_id(id: &str) -> Option<(&str, &str)> {
54 id.strip_prefix(REVIEWER_QUESTION_PREFIX)?
55 .split_once('.')
56 .filter(|(role, inner)| !role.is_empty() && !inner.is_empty())
57}
58
59impl RuntimeReviewView {
60 #[must_use]
63 pub fn is_working(&self) -> bool {
64 self.questions.is_empty()
66 && matches!(
67 self.phase,
68 TurnReviewPhase::CapturingDelta
69 | TurnReviewPhase::LaunchingReviewer
70 | TurnReviewPhase::Running { .. }
71 | TurnReviewPhase::Forwarding { error: None, .. }
72 )
73 }
74
75 #[must_use]
78 pub fn activity_label(&self) -> Option<&'static str> {
79 match &self.phase {
80 TurnReviewPhase::Resolved(_) => None,
81 _ if !self.questions.is_empty() => Some("Question"),
82 TurnReviewPhase::Forwarding { error: None, .. } => Some("Sending findings"),
83 TurnReviewPhase::Forwarding { error: Some(_), .. } => Some("Forward failed"),
84 TurnReviewPhase::Verdict(verdict) => Some(match verdict {
85 ReviewVerdict::Findings { .. } => "Findings",
86 ReviewVerdict::Failed { .. } => "Review failed",
87 ReviewVerdict::Clean => "Review complete",
88 }),
89 _ => Some("Reviewing"),
90 }
91 }
92}
93
94#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
96#[serde(deny_unknown_fields)]
97pub struct VerdictView {
98 pub kind: VerdictKind,
99 pub text: String,
102 pub allowed: Vec<Resolution>,
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
108#[serde(rename_all = "snake_case")]
109pub enum VerdictKind {
110 Clean,
111 Findings,
112 Failed,
113}
114
115#[must_use]
118pub fn role_session_id(primary_session_id: &str, role: &str) -> String {
119 if role == mj_core::review::driver::REVIEWER_ROLE {
120 format!("{primary_session_id}-reviewer")
121 } else {
122 format!("{primary_session_id}-review-{role}")
123 }
124}