sochdb_core/
predefined_views.rs

1// Copyright 2025 Sushanth (https://github.com/sushanthpy)
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Predefined SochQL Views (Task 7: Stable Identifiers)
16//!
17//! This module defines canonical views over the core memory schema
18//! (episodes, events, entities) that provide:
19//!
20//! 1. **Stable naming**: LLMs and humans learn consistent patterns
21//! 2. **Self-documenting**: View names encode semantics directly
22//! 3. **Reduced complexity**: O(1) concepts instead of O(T) tables
23//!
24//! ## Views
25//!
26//! | View | Purpose | Common Use |
27//! |------|---------|------------|
28//! | `conversation_view` | Chat messages | "Show recent conversation" |
29//! | `tool_calls_view` | Tool invocations | "What tools were used?" |
30//! | `error_view` | Error events | "Show recent errors" |
31//! | `episode_summary_view` | Episode overviews | "List recent tasks" |
32//! | `entity_directory_view` | Entity catalog | "Find user/project" |
33
34use std::collections::HashMap;
35
36/// A predefined view definition
37#[derive(Debug, Clone)]
38pub struct ViewDefinition {
39    /// View name (e.g., "conversation_view")
40    pub name: &'static str,
41    /// Human-readable description
42    pub description: &'static str,
43    /// SochQL definition (SELECT statement)
44    pub definition: &'static str,
45    /// Output columns
46    pub columns: &'static [&'static str],
47    /// Base table(s) this view queries
48    pub base_tables: &'static [&'static str],
49    /// Usage hints for LLMs
50    pub usage_hints: &'static [&'static str],
51}
52
53/// Get all predefined views
54pub fn get_predefined_views() -> Vec<ViewDefinition> {
55    vec![
56        // =====================================================================
57        // Conversation View - Chat history
58        // =====================================================================
59        ViewDefinition {
60            name: "conversation_view",
61            description: "Conversation history with role, content, and timestamp. \
62                          Use for displaying chat transcripts or analyzing dialogue patterns.",
63            definition: "SELECT episode_id, seq, role, content, ts \
64                         FROM events \
65                         WHERE event_type = 'message' \
66                         ORDER BY ts",
67            columns: &["episode_id", "seq", "role", "content", "ts"],
68            base_tables: &["events"],
69            usage_hints: &[
70                "Filter by episode_id to get a single conversation",
71                "Use LAST N to get recent messages",
72                "Filter by role to get only user or assistant messages",
73            ],
74        },
75        // =====================================================================
76        // Tool Calls View - Tool invocations
77        // =====================================================================
78        ViewDefinition {
79            name: "tool_calls_view",
80            description: "Tool invocations with inputs, outputs, and timing. \
81                          Use for debugging tool usage or analyzing patterns.",
82            definition: "SELECT episode_id, seq, tool_name, input, output, \
83                         tokens_in, tokens_out, latency_ms, ts \
84                         FROM events \
85                         WHERE event_type = 'tool_call' \
86                         ORDER BY ts",
87            columns: &[
88                "episode_id",
89                "seq",
90                "tool_name",
91                "input",
92                "output",
93                "tokens_in",
94                "tokens_out",
95                "latency_ms",
96                "ts",
97            ],
98            base_tables: &["events"],
99            usage_hints: &[
100                "Filter by tool_name to analyze specific tool usage",
101                "Sum tokens_in + tokens_out for cost analysis",
102                "Use AVG(latency_ms) GROUP BY tool_name for performance",
103            ],
104        },
105        // =====================================================================
106        // Error View - Errors and failures
107        // =====================================================================
108        ViewDefinition {
109            name: "error_view",
110            description: "Error events with context and stack traces. \
111                          Use for debugging failures or monitoring error rates.",
112            definition: "SELECT episode_id, seq, error_type, message, \
113                         stack_trace, ts \
114                         FROM events \
115                         WHERE event_type = 'error' \
116                         ORDER BY ts DESC",
117            columns: &[
118                "episode_id",
119                "seq",
120                "error_type",
121                "message",
122                "stack_trace",
123                "ts",
124            ],
125            base_tables: &["events"],
126            usage_hints: &[
127                "Use LAST N for recent errors",
128                "Group by error_type for error categorization",
129                "Join with episodes for context on when errors occurred",
130            ],
131        },
132        // =====================================================================
133        // Episode Summary View - Task/conversation overviews
134        // =====================================================================
135        ViewDefinition {
136            name: "episode_summary_view",
137            description: "Episode summaries for quick browsing. \
138                          Use for listing recent tasks or finding similar past work.",
139            definition: "SELECT episode_id, episode_type, summary, tags, \
140                         ts_start, ts_end \
141                         FROM episodes \
142                         ORDER BY ts_start DESC",
143            columns: &[
144                "episode_id",
145                "episode_type",
146                "summary",
147                "tags",
148                "ts_start",
149                "ts_end",
150            ],
151            base_tables: &["episodes"],
152            usage_hints: &[
153                "Use memory.search_episodes for semantic search",
154                "Filter by episode_type for specific task types",
155                "Calculate ts_end - ts_start for duration",
156            ],
157        },
158        // =====================================================================
159        // Entity Directory View - Entity catalog
160        // =====================================================================
161        ViewDefinition {
162            name: "entity_directory_view",
163            description: "Entity directory organized by kind. \
164                          Use for finding users, projects, services, or documents.",
165            definition: "SELECT entity_id, kind, name, description, updated_at \
166                         FROM entities \
167                         ORDER BY kind, name",
168            columns: &["entity_id", "kind", "name", "description", "updated_at"],
169            base_tables: &["entities"],
170            usage_hints: &[
171                "Use memory.search_entities for semantic search",
172                "Filter by kind = 'user' or 'project' etc.",
173                "Use memory.get_entity_facts for full details",
174            ],
175        },
176        // =====================================================================
177        // Session Timeline View - Full session history
178        // =====================================================================
179        ViewDefinition {
180            name: "session_timeline_view",
181            description: "Complete session timeline combining messages and tool calls. \
182                          Use for full session replay or context building.",
183            definition: "SELECT episode_id, seq, event_type, role, content, \
184                         tool_name, ts \
185                         FROM events \
186                         WHERE event_type IN ('message', 'tool_call', 'tool_result') \
187                         ORDER BY episode_id, seq",
188            columns: &[
189                "episode_id",
190                "seq",
191                "event_type",
192                "role",
193                "content",
194                "tool_name",
195                "ts",
196            ],
197            base_tables: &["events"],
198            usage_hints: &[
199                "Filter by episode_id for a single session",
200                "Use logs.tail for recent events",
201                "Interleaves messages and tool calls chronologically",
202            ],
203        },
204        // =====================================================================
205        // Metrics View - Performance and cost metrics
206        // =====================================================================
207        ViewDefinition {
208            name: "metrics_view",
209            description: "Aggregated metrics per episode. \
210                          Use for cost analysis, performance monitoring.",
211            definition: "SELECT episode_id, \
212                         COUNT(*) as event_count, \
213                         SUM(tokens_in) as total_tokens_in, \
214                         SUM(tokens_out) as total_tokens_out, \
215                         AVG(latency_ms) as avg_latency_ms, \
216                         MIN(ts) as first_event, \
217                         MAX(ts) as last_event \
218                         FROM events \
219                         GROUP BY episode_id",
220            columns: &[
221                "episode_id",
222                "event_count",
223                "total_tokens_in",
224                "total_tokens_out",
225                "avg_latency_ms",
226                "first_event",
227                "last_event",
228            ],
229            base_tables: &["events"],
230            usage_hints: &[
231                "Join with episodes for episode metadata",
232                "Use for cost estimation and budgeting",
233                "Calculate total_tokens_in + total_tokens_out for total usage",
234            ],
235        },
236    ]
237}
238
239/// Get a view by name
240pub fn get_view(name: &str) -> Option<ViewDefinition> {
241    get_predefined_views().into_iter().find(|v| v.name == name)
242}
243
244/// Build a HashMap of views for O(1) lookup
245pub fn build_view_map() -> HashMap<&'static str, ViewDefinition> {
246    get_predefined_views()
247        .into_iter()
248        .map(|v| (v.name, v))
249        .collect()
250}
251
252// ============================================================================
253// Naming Conventions
254// ============================================================================
255
256/// Standard column naming conventions
257pub mod naming {
258    /// ID columns use `<entity>_id` format
259    pub const ID_SUFFIX: &str = "_id";
260
261    /// Timestamp columns use `ts_<event>` or just `ts`
262    pub const TS_PREFIX: &str = "ts_";
263
264    /// Count columns use `<thing>_count`
265    pub const COUNT_SUFFIX: &str = "_count";
266
267    /// Boolean columns use `is_<state>` or `has_<thing>`
268    pub const BOOL_IS_PREFIX: &str = "is_";
269    pub const BOOL_HAS_PREFIX: &str = "has_";
270
271    /// Standard role values
272    pub const ROLES: &[&str] = &["user", "assistant", "system", "tool", "external"];
273
274    /// Standard event types
275    pub const EVENT_TYPES: &[&str] = &[
276        "message",
277        "tool_call",
278        "tool_result",
279        "error",
280        "start",
281        "end",
282        "checkpoint",
283        "observation",
284    ];
285
286    /// Standard entity kinds
287    pub const ENTITY_KINDS: &[&str] = &[
288        "user",
289        "project",
290        "document",
291        "service",
292        "agent",
293        "organization",
294        "concept",
295    ];
296
297    /// Standard episode types
298    pub const EPISODE_TYPES: &[&str] = &[
299        "conversation",
300        "task",
301        "workflow",
302        "debug",
303        "agent_interaction",
304        "batch_job",
305    ];
306}
307
308// ============================================================================
309// Tests
310// ============================================================================
311
312#[cfg(test)]
313mod tests {
314    use super::*;
315
316    #[test]
317    fn test_get_predefined_views() {
318        let views = get_predefined_views();
319        assert!(views.len() >= 5, "Should have at least 5 predefined views");
320
321        // Check conversation_view exists
322        let conv = views.iter().find(|v| v.name == "conversation_view");
323        assert!(conv.is_some());
324
325        let conv = conv.unwrap();
326        assert!(conv.columns.contains(&"role"));
327        assert!(conv.columns.contains(&"content"));
328    }
329
330    #[test]
331    fn test_get_view() {
332        let view = get_view("tool_calls_view");
333        assert!(view.is_some());
334
335        let view = view.unwrap();
336        assert!(view.columns.contains(&"tool_name"));
337        assert!(view.base_tables.contains(&"events"));
338    }
339
340    #[test]
341    fn test_build_view_map() {
342        let map = build_view_map();
343        assert!(map.contains_key("error_view"));
344        assert!(map.contains_key("episode_summary_view"));
345    }
346
347    #[test]
348    fn test_naming_conventions() {
349        use naming::*;
350
351        assert!(ROLES.contains(&"user"));
352        assert!(ROLES.contains(&"assistant"));
353        assert!(EVENT_TYPES.contains(&"tool_call"));
354        assert!(ENTITY_KINDS.contains(&"project"));
355    }
356}