1use crate::core::event::{Event, SessionRecord};
5use crate::feedback::types::FeedbackRecord;
6use crate::metrics::types::{FileFact, ToolSpanView};
7use crate::store::{SessionOutcomeRow, SessionSampleAgg};
8use serde::{Deserialize, Serialize};
9use std::collections::{HashMap, HashSet};
10
11#[derive(Debug, Clone)]
13pub struct Inputs {
14 pub window_start_ms: u64,
15 pub window_end_ms: u64,
16 pub events: Vec<(SessionRecord, Event)>,
18 pub files_touched: Vec<(String, String)>,
19 pub skills_used: Vec<(String, String)>,
20 pub tool_spans: Vec<ToolSpanView>,
21 pub skills_used_recent_slugs: HashSet<String>,
23 pub usage_lookback_ms: u64,
24 pub skill_files_on_disk: Vec<SkillFileOnDisk>,
25 pub rule_files_on_disk: Vec<SkillFileOnDisk>,
27 pub rules_used_recent_slugs: HashSet<String>,
28 pub file_facts: HashMap<String, FileFact>,
29 pub aggregates: RetroAggregates,
30 pub eval_scores: Vec<(String, f64)>,
32 pub prompt_fingerprints: Vec<(String, String)>,
34 pub feedback: Vec<FeedbackRecord>,
36 pub session_outcomes: Vec<SessionOutcomeRow>,
38 pub session_sample_aggs: Vec<SessionSampleAgg>,
40}
41
42#[derive(Debug, Clone)]
43pub struct SkillFileOnDisk {
44 pub slug: String,
45 pub size_bytes: u64,
47 pub mtime_ms: u64,
48}
49
50#[derive(Debug, Clone, Default)]
51pub struct RetroAggregates {
52 pub unique_session_ids: HashSet<String>,
53 pub tool_event_counts: HashMap<String, u64>,
54 pub tool_cost_usd_e6: HashMap<String, i64>,
55 pub model_session_counts: HashMap<String, u64>,
56 pub total_cost_usd_e6: i64,
57 pub span_tree_stats: Option<SpanTreeStats>,
58}
59
60#[derive(Debug, Clone)]
61pub struct SpanTreeStats {
62 pub max_depth: u32,
63 pub max_fan_out: u32,
64 pub deepest_span_id: String,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
69#[serde(rename_all = "snake_case")]
70pub enum Confidence {
71 High,
72 Medium,
73 Low,
74}
75
76impl Confidence {
77 pub fn weight(self) -> f64 {
78 match self {
79 Self::High => 1.0,
80 Self::Medium => 0.6,
81 Self::Low => 0.3,
82 }
83 }
84
85 pub fn label(self) -> &'static str {
86 match self {
87 Self::High => "High",
88 Self::Medium => "Medium",
89 Self::Low => "Low",
90 }
91 }
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
96#[serde(rename_all = "snake_case")]
97pub enum BetCategory {
98 QuickWin,
99 Investigation,
100 Hygiene,
101}
102
103impl BetCategory {
104 pub fn label(self) -> &'static str {
105 match self {
106 Self::QuickWin => "quick_win",
107 Self::Investigation => "investigation",
108 Self::Hygiene => "hygiene",
109 }
110 }
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
115pub struct Bet {
116 pub id: String,
118 pub heuristic_id: String,
119 pub title: String,
120 pub hypothesis: String,
121 pub expected_tokens_saved_per_week: f64,
122 pub effort_minutes: u32,
123 pub evidence: Vec<String>,
124 pub apply_step: String,
125 #[serde(default)]
126 pub evidence_recency_ms: u64,
127 #[serde(default, skip_serializing_if = "Option::is_none")]
128 pub confidence: Option<Confidence>,
129 #[serde(default, skip_serializing_if = "Option::is_none")]
130 pub category: Option<BetCategory>,
131}
132
133impl Bet {
134 pub fn score(&self) -> f64 {
135 let weight = self.confidence.map_or(1.0, Confidence::weight);
136 weight * self.expected_tokens_saved_per_week / (self.effort_minutes as f64 + 1.0)
137 }
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
141pub struct RetroMeta {
142 pub week_label: String,
143 pub span_start_ms: u64,
144 pub span_end_ms: u64,
145 pub session_count: u64,
146 pub total_cost_usd_e6: i64,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
150pub struct RetroStats {
151 pub sessions: u64,
152 pub total_cost_usd_e6: i64,
153 pub top_model: Option<String>,
154 pub top_model_pct: Option<u64>,
155 pub top_tool: Option<String>,
156 pub top_tool_pct: Option<u64>,
157 pub median_session_minutes: Option<u64>,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
162pub struct Report {
163 pub meta: RetroMeta,
164 pub top_bets: Vec<Bet>,
165 pub skipped_deduped: Vec<String>,
166 pub stats: RetroStats,
167}