Skip to main content

csusage_test_support/
claude_science.rs

1use std::path::Path;
2
3use sqlite::Connection;
4
5/// Creates the schema and representative rows used by Claude Science report
6/// tests. The layout mirrors the Claude Science metadata database: one row
7/// per conversation frame, with `root_frame_id` grouping sub-agent frames
8/// into their parent session.
9pub fn create_fixture(path: impl AsRef<Path>) {
10    let db = sqlite::open(path).unwrap();
11    db.execute(
12        "CREATE TABLE frames (
13            id TEXT PRIMARY KEY,
14            parent_frame_id TEXT,
15            root_frame_id TEXT,
16            agent_name TEXT NOT NULL,
17            status TEXT NOT NULL,
18            model TEXT,
19            project_id TEXT,
20            input_tokens INTEGER,
21            output_tokens INTEGER,
22            cache_read_tokens INTEGER,
23            cache_write_tokens INTEGER,
24            total_cost REAL,
25            created_at INTEGER NOT NULL,
26            updated_at INTEGER NOT NULL
27        )",
28    )
29    .unwrap();
30    db.execute("CREATE TABLE projects (id TEXT PRIMARY KEY, name TEXT)")
31        .unwrap();
32    db.execute("INSERT INTO projects VALUES ('proj_alpha', 'alpha'), ('proj_beta', 'beta')")
33        .unwrap();
34    insert_frame(
35        &db,
36        FixtureFrame {
37            id: "frame-1",
38            parent_frame_id: None,
39            root_frame_id: "frame-1",
40            model: "claude-sonnet-4-5",
41            project_id: "proj_alpha",
42            input_tokens: 100,
43            output_tokens: 10,
44            cache_read_tokens: 25,
45            cache_write_tokens: 15,
46            total_cost: Some(0.5),
47            timestamp: "2099-01-02T00:00:00.000Z",
48        },
49    );
50    insert_frame(
51        &db,
52        FixtureFrame {
53            id: "frame-2",
54            parent_frame_id: Some("frame-1"),
55            root_frame_id: "frame-1",
56            model: "cs-switch-direct:claude-sonnet-4-5",
57            project_id: "proj_alpha",
58            input_tokens: 200,
59            output_tokens: 20,
60            cache_read_tokens: 40,
61            cache_write_tokens: 30,
62            total_cost: Some(1.0),
63            timestamp: "2099-01-15T12:00:00.000Z",
64        },
65    );
66    insert_frame(
67        &db,
68        FixtureFrame {
69            id: "frame-3",
70            parent_frame_id: None,
71            root_frame_id: "frame-3",
72            model: "claude-opus-4-6",
73            project_id: "proj_beta",
74            input_tokens: 50,
75            output_tokens: 5,
76            cache_read_tokens: 10,
77            cache_write_tokens: 0,
78            total_cost: Some(0.25),
79            timestamp: "2099-02-01T00:00:00.000Z",
80        },
81    );
82    // A frame without a recorded cost exercises the pricing-catalog fallback.
83    insert_frame(
84        &db,
85        FixtureFrame {
86            id: "frame-4",
87            parent_frame_id: None,
88            root_frame_id: "frame-4",
89            model: "claude-sonnet-4-5",
90            project_id: "proj_alpha",
91            input_tokens: 40,
92            output_tokens: 4,
93            cache_read_tokens: 5,
94            cache_write_tokens: 0,
95            total_cost: None,
96            timestamp: "2099-03-01T00:00:00.000Z",
97        },
98    );
99}
100
101/// Describes one completed frame row for a Claude Science fixture.
102struct FixtureFrame<'a> {
103    id: &'a str,
104    parent_frame_id: Option<&'a str>,
105    root_frame_id: &'a str,
106    model: &'a str,
107    project_id: &'a str,
108    input_tokens: i64,
109    output_tokens: i64,
110    cache_read_tokens: i64,
111    cache_write_tokens: i64,
112    total_cost: Option<f64>,
113    timestamp: &'a str,
114}
115
116/// Inserts one completed frame row into a Claude Science fixture database.
117fn insert_frame(db: &Connection, frame: FixtureFrame<'_>) {
118    let mut statement = db
119        .prepare(
120            "INSERT INTO frames
121             (id, parent_frame_id, root_frame_id, agent_name, status, model, project_id,
122              input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, total_cost,
123              created_at, updated_at)
124             VALUES
125              (?1, ?2, ?3, 'OPERON', 'completed', ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?11)",
126        )
127        .unwrap();
128    let millis = frame
129        .timestamp
130        .parse::<jiff::Timestamp>()
131        .unwrap()
132        .as_millisecond();
133    statement.bind((1, frame.id)).unwrap();
134    statement
135        .bind((
136            2,
137            frame
138                .parent_frame_id
139                .map(sqlite::Value::from)
140                .unwrap_or(sqlite::Value::Null),
141        ))
142        .unwrap();
143    statement.bind((3, frame.root_frame_id)).unwrap();
144    statement.bind((4, frame.model)).unwrap();
145    statement.bind((5, frame.project_id)).unwrap();
146    statement.bind((6, frame.input_tokens)).unwrap();
147    statement.bind((7, frame.output_tokens)).unwrap();
148    statement.bind((8, frame.cache_read_tokens)).unwrap();
149    statement.bind((9, frame.cache_write_tokens)).unwrap();
150    statement
151        .bind((
152            10,
153            frame
154                .total_cost
155                .map(sqlite::Value::from)
156                .unwrap_or(sqlite::Value::Null),
157        ))
158        .unwrap();
159    statement.bind((11, millis)).unwrap();
160    statement.next().unwrap();
161}