Skip to main content

context69_contracts/
search.rs

1use chrono::{DateTime, NaiveDate, Utc};
2use rmcp::schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use utoipa::ToSchema;
6use uuid::Uuid;
7
8use crate::Visibility;
9
10#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, JsonSchema)]
11pub struct SearchRequest {
12    pub query: String,
13    #[serde(default = "default_limit")]
14    pub limit: usize,
15    #[serde(default)]
16    pub source_key: Option<String>,
17    #[serde(default)]
18    pub group_key: Option<String>,
19    #[serde(default)]
20    pub project_key: Option<String>,
21    #[serde(default)]
22    pub published_after: Option<NaiveDate>,
23    #[serde(default)]
24    pub published_before: Option<NaiveDate>,
25}
26
27fn default_limit() -> usize {
28    8
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
32#[serde(rename_all = "snake_case")]
33pub enum SearchMode {
34    Vector,
35    Hybrid,
36}
37
38impl SearchMode {
39    pub fn as_str(self) -> &'static str {
40        match self {
41            Self::Vector => "vector",
42            Self::Hybrid => "hybrid",
43        }
44    }
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, JsonSchema)]
48pub struct SearchHit {
49    pub chunk_id: Uuid,
50    pub document_id: i64,
51    pub group_key: String,
52    pub project_key: String,
53    pub visibility: Visibility,
54    pub source_key: String,
55    pub external_id: String,
56    pub title: String,
57    pub summary: Option<String>,
58    pub source_uri: String,
59    pub published_at: Option<NaiveDate>,
60    pub chunk_index: i32,
61    pub chunk_text: String,
62    pub score: f32,
63    #[serde(default, skip_serializing_if = "Option::is_none")]
64    pub vector_score: Option<f32>,
65    #[serde(default, skip_serializing_if = "Option::is_none")]
66    pub keyword_score: Option<f32>,
67    #[serde(default, skip_serializing_if = "Option::is_none")]
68    pub rerank_score: Option<f32>,
69    #[serde(default, skip_serializing_if = "Option::is_none")]
70    pub match_reason: Option<String>,
71    #[serde(default)]
72    #[schema(value_type = Object)]
73    pub metadata_json: Value,
74    #[serde(default, skip_serializing_if = "Option::is_none")]
75    pub library_file_id: Option<Uuid>,
76    #[serde(default, skip_serializing_if = "Option::is_none")]
77    pub library_section_label: Option<String>,
78    #[serde(default, skip_serializing_if = "Option::is_none")]
79    pub library_path: Option<String>,
80    #[serde(default)]
81    pub is_library_file: bool,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, JsonSchema)]
85pub struct SearchResponse {
86    pub query: String,
87    pub hits: Vec<SearchHit>,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, JsonSchema)]
91pub struct DocumentResponse {
92    pub document_id: i64,
93    pub group_key: String,
94    pub project_key: String,
95    pub visibility: Visibility,
96    pub source_key: String,
97    pub external_id: String,
98    pub title: String,
99    pub summary: Option<String>,
100    pub source_uri: String,
101    pub published_at: Option<NaiveDate>,
102    pub updated_at: DateTime<Utc>,
103    pub record_hash: String,
104    #[serde(default)]
105    #[schema(value_type = Object)]
106    pub metadata_json: Value,
107    #[serde(default, skip_serializing_if = "Option::is_none")]
108    pub library_file_id: Option<Uuid>,
109    #[serde(default, skip_serializing_if = "Option::is_none")]
110    pub library_section_label: Option<String>,
111    #[serde(default, skip_serializing_if = "Option::is_none")]
112    pub library_path: Option<String>,
113    #[serde(default)]
114    pub is_library_file: bool,
115    pub chunks: Vec<DocumentChunkResponse>,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, JsonSchema)]
119pub struct DocumentChunkResponse {
120    pub chunk_id: Uuid,
121    pub chunk_index: i32,
122    pub text: String,
123}