1use crate::feedback::types::{FeedbackLabel, FeedbackRecord, FeedbackScore};
3use crate::store::Store;
4use anyhow::Result;
5use std::path::Path;
6
7fn now_ms() -> u64 {
8 std::time::SystemTime::now()
9 .duration_since(std::time::UNIX_EPOCH)
10 .unwrap_or_default()
11 .as_millis() as u64
12}
13
14fn parse_since(since: &str) -> Option<u64> {
15 let s = since.trim();
16 let (n, unit) = s.split_at(s.len().saturating_sub(1));
17 let days: u64 = match unit {
18 "d" => n.parse().ok()?,
19 "w" => n.parse::<u64>().ok()?.checked_mul(7)?,
20 _ => return None,
21 };
22 Some(days * 86_400_000)
23}
24
25fn open_store(workspace: Option<&Path>) -> Result<Store> {
26 let ws = crate::core::workspace::resolve(workspace)?;
27 Store::open(&crate::core::workspace::db_path(&ws)?)
28}
29
30pub fn cmd_sessions_annotate(
32 id: &str,
33 score: Option<u8>,
34 label: Option<FeedbackLabel>,
35 note: Option<String>,
36 workspace: Option<&Path>,
37) -> Result<()> {
38 let store = open_store(workspace)?;
39 let record = FeedbackRecord {
40 id: uuid::Uuid::now_v7().to_string(),
41 session_id: id.to_string(),
42 score: score.and_then(FeedbackScore::new),
43 label,
44 note,
45 created_at_ms: now_ms(),
46 };
47 store.upsert_feedback(&record)?;
48 println!("annotated session {id}");
49 Ok(())
50}
51
52pub fn cmd_feedback_list(
54 workspace: Option<&Path>,
55 label_filter: Option<FeedbackLabel>,
56 since: Option<String>,
57 json: bool,
58) -> Result<()> {
59 let store = open_store(workspace)?;
60 let now = now_ms();
61 let start_ms = since
62 .as_deref()
63 .and_then(parse_since)
64 .map(|d| now.saturating_sub(d))
65 .unwrap_or(0);
66 let mut records = store.list_feedback_in_window(start_ms, now)?;
67 if let Some(lf) = &label_filter {
68 records.retain(|r| r.label.as_ref() == Some(lf));
69 }
70 if json {
71 println!("{}", serde_json::to_string_pretty(&records)?);
72 return Ok(());
73 }
74 if records.is_empty() {
75 println!("no feedback records");
76 return Ok(());
77 }
78 for r in &records {
79 let score = r
80 .score
81 .as_ref()
82 .map(|s| s.0.to_string())
83 .unwrap_or_else(|| "-".into());
84 let label = r
85 .label
86 .as_ref()
87 .map(|l| l.to_string())
88 .unwrap_or_else(|| "-".into());
89 let note = r.note.as_deref().unwrap_or("-");
90 println!(
91 "{} session={} score={} label={} note={}",
92 r.id, r.session_id, score, label, note
93 );
94 }
95 Ok(())
96}