spreadsheet_mcp/
model.rs

1use crate::caps::BackendCaps;
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4use std::collections::BTreeMap;
5
6#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, Default)]
7#[serde(transparent)]
8pub struct WorkbookId(pub String);
9
10impl WorkbookId {
11    pub fn as_str(&self) -> &str {
12        &self.0
13    }
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
17pub struct WorkbookDescriptor {
18    pub workbook_id: WorkbookId,
19    pub short_id: String,
20    pub slug: String,
21    pub folder: Option<String>,
22    pub path: String,
23    pub bytes: u64,
24    pub last_modified: Option<String>,
25    pub caps: BackendCaps,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
29pub struct WorkbookListResponse {
30    pub workbooks: Vec<WorkbookDescriptor>,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
34pub struct WorkbookDescription {
35    pub workbook_id: WorkbookId,
36    pub short_id: String,
37    pub slug: String,
38    pub path: String,
39    pub bytes: u64,
40    pub sheet_count: usize,
41    pub defined_names: usize,
42    pub tables: usize,
43    pub macros_present: bool,
44    pub last_modified: Option<String>,
45    pub caps: BackendCaps,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
49pub struct WorkbookSummaryResponse {
50    pub workbook_id: WorkbookId,
51    pub workbook_short_id: String,
52    pub slug: String,
53    pub sheet_count: usize,
54    pub total_cells: u64,
55    pub total_formulas: u64,
56    pub breakdown: WorkbookBreakdown,
57    pub region_counts: RegionCountSummary,
58    pub key_named_ranges: Vec<NamedRangeDescriptor>,
59    pub suggested_entry_points: Vec<EntryPoint>,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
63pub struct WorkbookBreakdown {
64    pub data_sheets: u32,
65    pub calculator_sheets: u32,
66    pub parameter_sheets: u32,
67    pub metadata_sheets: u32,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
71pub struct RegionCountSummary {
72    pub data: u32,
73    pub parameters: u32,
74    pub outputs: u32,
75    pub calculator: u32,
76    pub metadata: u32,
77    pub other: u32,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
81pub struct EntryPoint {
82    pub sheet_name: String,
83    pub region_id: Option<u32>,
84    pub bounds: Option<String>,
85    pub rationale: String,
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
89pub struct SheetSummary {
90    pub name: String,
91    pub visible: bool,
92    pub row_count: u32,
93    pub column_count: u32,
94    pub non_empty_cells: u32,
95    pub formula_cells: u32,
96    pub cached_values: u32,
97    pub classification: SheetClassification,
98    pub style_tags: Vec<String>,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
102#[serde(rename_all = "snake_case")]
103pub enum SheetClassification {
104    Data,
105    Calculator,
106    Mixed,
107    Metadata,
108    Empty,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
112pub struct SheetListResponse {
113    pub workbook_id: WorkbookId,
114    pub workbook_short_id: String,
115    pub sheets: Vec<SheetSummary>,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
119pub struct SheetOverviewResponse {
120    pub workbook_id: WorkbookId,
121    pub workbook_short_id: String,
122    pub sheet_name: String,
123    pub narrative: String,
124    pub regions: Vec<SheetRegion>,
125    pub detected_regions: Vec<DetectedRegion>,
126    pub key_ranges: Vec<String>,
127    pub formula_ratio: f32,
128    pub notable_features: Vec<String>,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
132pub struct SheetRegion {
133    pub kind: RegionKind,
134    pub address: String,
135    pub description: String,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
139pub enum RegionKind {
140    #[serde(rename = "likely_table")]
141    Table,
142    #[serde(rename = "likely_data")]
143    Data,
144    #[serde(rename = "likely_parameters")]
145    Parameters,
146    #[serde(rename = "likely_outputs")]
147    Outputs,
148    #[serde(rename = "likely_calculator")]
149    Calculator,
150    #[serde(rename = "likely_metadata")]
151    Metadata,
152    #[serde(rename = "likely_styles")]
153    Styles,
154    #[serde(rename = "likely_comments")]
155    Comments,
156    #[serde(rename = "unknown")]
157    Other,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
161pub struct DetectedRegion {
162    pub id: u32,
163    pub bounds: String,
164    pub header_row: Option<u32>,
165    pub headers: Vec<String>,
166    pub row_count: u32,
167    pub classification: RegionKind,
168    pub region_kind: Option<RegionKind>,
169    pub confidence: f32,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
173pub struct SheetPageResponse {
174    pub workbook_id: WorkbookId,
175    pub workbook_short_id: String,
176    pub sheet_name: String,
177    pub rows: Vec<RowSnapshot>,
178    pub has_more: bool,
179    pub next_start_row: Option<u32>,
180    pub header_row: Option<RowSnapshot>,
181    pub compact: Option<SheetPageCompact>,
182    pub values_only: Option<SheetPageValues>,
183    pub format: SheetPageFormat,
184}
185
186#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
187pub struct RowSnapshot {
188    pub row_index: u32,
189    pub cells: Vec<CellSnapshot>,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
193pub struct CellSnapshot {
194    pub address: String,
195    pub value: Option<CellValue>,
196    pub formula: Option<String>,
197    pub cached_value: Option<CellValue>,
198    pub number_format: Option<String>,
199    pub style_tags: Vec<String>,
200    pub notes: Vec<String>,
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
204#[serde(tag = "kind", content = "value")]
205pub enum CellValue {
206    Text(String),
207    Number(f64),
208    Bool(bool),
209    Error(String),
210    Date(String),
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
214#[serde(rename_all = "snake_case")]
215#[derive(Default)]
216pub enum SheetPageFormat {
217    #[default]
218    Full,
219    Compact,
220    ValuesOnly,
221}
222
223#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
224pub struct SheetPageCompact {
225    pub headers: Vec<String>,
226    pub header_row: Vec<Option<CellValue>>,
227    pub rows: Vec<Vec<Option<CellValue>>>,
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
231pub struct SheetPageValues {
232    pub rows: Vec<Vec<Option<CellValue>>>,
233}
234
235#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
236pub struct SheetStatisticsResponse {
237    pub workbook_id: WorkbookId,
238    pub workbook_short_id: String,
239    pub sheet_name: String,
240    pub row_count: u32,
241    pub column_count: u32,
242    pub density: f32,
243    pub numeric_columns: Vec<ColumnSummary>,
244    pub text_columns: Vec<ColumnSummary>,
245    pub null_counts: BTreeMap<String, u32>,
246    pub duplicate_warnings: Vec<String>,
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
250pub struct ColumnSummary {
251    pub header: Option<String>,
252    pub column: String,
253    pub samples: Vec<CellValue>,
254    pub min: Option<f64>,
255    pub max: Option<f64>,
256    pub mean: Option<f64>,
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
260pub struct SheetFormulaMapResponse {
261    pub workbook_id: WorkbookId,
262    pub workbook_short_id: String,
263    pub sheet_name: String,
264    pub groups: Vec<FormulaGroup>,
265    pub truncated: bool,
266}
267
268#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
269pub struct FormulaGroup {
270    pub fingerprint: String,
271    pub addresses: Vec<String>,
272    pub formula: String,
273    pub is_array: bool,
274    pub is_shared: bool,
275    pub is_volatile: bool,
276}
277
278#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
279pub struct FormulaTraceResponse {
280    pub workbook_id: WorkbookId,
281    pub workbook_short_id: String,
282    pub sheet_name: String,
283    pub origin: String,
284    pub direction: TraceDirection,
285    pub layers: Vec<TraceLayer>,
286    pub next_cursor: Option<TraceCursor>,
287    pub notes: Vec<String>,
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
291pub struct FormulaTraceEdge {
292    pub from: String,
293    pub to: String,
294    pub formula: Option<String>,
295    pub note: Option<String>,
296}
297
298#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
299pub struct TraceLayer {
300    pub depth: u32,
301    pub summary: TraceLayerSummary,
302    pub highlights: TraceLayerHighlights,
303    pub edges: Vec<FormulaTraceEdge>,
304    pub has_more: bool,
305}
306
307#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
308pub struct TraceLayerSummary {
309    pub total_nodes: usize,
310    pub formula_nodes: usize,
311    pub value_nodes: usize,
312    pub blank_nodes: usize,
313    pub external_nodes: usize,
314    pub unique_formula_groups: usize,
315}
316
317#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
318pub struct TraceLayerHighlights {
319    pub top_ranges: Vec<TraceRangeHighlight>,
320    pub top_formula_groups: Vec<TraceFormulaGroupHighlight>,
321    pub notable_cells: Vec<TraceCellHighlight>,
322}
323
324#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
325pub struct TraceRangeHighlight {
326    pub start: String,
327    pub end: String,
328    pub count: usize,
329    pub literals: usize,
330    pub formulas: usize,
331    pub blanks: usize,
332    pub sample_values: Vec<CellValue>,
333    pub sample_formulas: Vec<String>,
334    pub sample_addresses: Vec<String>,
335}
336
337#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
338pub struct TraceFormulaGroupHighlight {
339    pub fingerprint: String,
340    pub formula: String,
341    pub count: usize,
342    pub sample_addresses: Vec<String>,
343}
344
345#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
346pub struct TraceCellHighlight {
347    pub address: String,
348    pub kind: TraceCellKind,
349    pub value: Option<CellValue>,
350    pub formula: Option<String>,
351}
352
353#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
354#[serde(rename_all = "snake_case")]
355pub enum TraceCellKind {
356    Formula,
357    Literal,
358    Blank,
359    External,
360}
361
362#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
363pub struct TraceCursor {
364    pub depth: u32,
365    pub offset: usize,
366}
367
368#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
369#[serde(rename_all = "snake_case")]
370pub enum TraceDirection {
371    Precedents,
372    Dependents,
373}
374
375#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
376pub struct NamedRangeDescriptor {
377    pub name: String,
378    pub scope: Option<String>,
379    pub refers_to: String,
380    pub kind: NamedItemKind,
381    pub sheet_name: Option<String>,
382    pub comment: Option<String>,
383}
384
385#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
386#[serde(rename_all = "snake_case")]
387pub enum NamedItemKind {
388    NamedRange,
389    Table,
390    Formula,
391    Unknown,
392}
393
394#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
395pub struct NamedRangesResponse {
396    pub workbook_id: WorkbookId,
397    pub workbook_short_id: String,
398    pub items: Vec<NamedRangeDescriptor>,
399}
400
401#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
402pub struct FindFormulaMatch {
403    pub address: String,
404    pub sheet_name: String,
405    pub formula: String,
406    pub cached_value: Option<CellValue>,
407    pub context: Vec<RowSnapshot>,
408}
409
410#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
411pub struct FindFormulaResponse {
412    pub workbook_id: WorkbookId,
413    pub workbook_short_id: String,
414    pub matches: Vec<FindFormulaMatch>,
415}
416
417#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
418pub struct VolatileScanEntry {
419    pub address: String,
420    pub sheet_name: String,
421    pub function: String,
422    pub note: Option<String>,
423}
424
425#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
426pub struct VolatileScanResponse {
427    pub workbook_id: WorkbookId,
428    pub workbook_short_id: String,
429    pub items: Vec<VolatileScanEntry>,
430    pub truncated: bool,
431}
432
433#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
434pub struct SheetStylesResponse {
435    pub workbook_id: WorkbookId,
436    pub workbook_short_id: String,
437    pub sheet_name: String,
438    pub styles: Vec<StyleSummary>,
439    pub conditional_rules: Vec<String>,
440}
441
442#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
443pub struct StyleSummary {
444    pub style_id: String,
445    pub occurrences: u32,
446    pub tags: Vec<String>,
447    pub example_cells: Vec<String>,
448}
449
450#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
451pub struct ManifestStubResponse {
452    pub workbook_id: WorkbookId,
453    pub workbook_short_id: String,
454    pub slug: String,
455    pub sheets: Vec<ManifestSheetStub>,
456}
457
458#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
459pub struct ManifestSheetStub {
460    pub sheet_name: String,
461    pub classification: SheetClassification,
462    pub candidate_expectations: Vec<String>,
463    pub notes: Vec<String>,
464}
465
466#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Default)]
467#[serde(rename_all = "snake_case")]
468pub enum FindMode {
469    #[default]
470    Value,
471    Label,
472}
473
474#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
475#[serde(rename_all = "snake_case")]
476pub enum LabelDirection {
477    Right,
478    Below,
479    Any,
480}
481
482#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
483pub struct FindValueMatch {
484    pub address: String,
485    pub sheet_name: String,
486    pub value: Option<CellValue>,
487    pub row_context: Option<RowContext>,
488    pub neighbors: Option<NeighborValues>,
489    pub label_hit: Option<LabelHit>,
490}
491
492#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
493pub struct RowContext {
494    pub headers: Vec<String>,
495    pub values: Vec<Option<CellValue>>,
496}
497
498#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
499pub struct NeighborValues {
500    pub left: Option<CellValue>,
501    pub right: Option<CellValue>,
502    pub up: Option<CellValue>,
503    pub down: Option<CellValue>,
504}
505
506#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
507pub struct LabelHit {
508    pub label_address: String,
509    pub label: String,
510}
511
512#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
513pub struct FindValueResponse {
514    pub workbook_id: WorkbookId,
515    pub workbook_short_id: String,
516    pub matches: Vec<FindValueMatch>,
517    pub truncated: bool,
518}
519
520pub type TableRow = BTreeMap<String, Option<CellValue>>;
521
522#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
523pub struct ReadTableResponse {
524    pub workbook_id: WorkbookId,
525    pub workbook_short_id: String,
526    pub sheet_name: String,
527    pub table_name: Option<String>,
528    pub headers: Vec<String>,
529    pub rows: Vec<TableRow>,
530    pub total_rows: u32,
531    pub has_more: bool,
532}
533
534#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
535pub struct ColumnTypeSummary {
536    pub name: String,
537    pub inferred_type: String,
538    pub nulls: u32,
539    pub distinct: u32,
540    pub top_values: Vec<String>,
541    pub min: Option<f64>,
542    pub max: Option<f64>,
543    pub mean: Option<f64>,
544}
545
546#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
547pub struct TableProfileResponse {
548    pub workbook_id: WorkbookId,
549    pub workbook_short_id: String,
550    pub sheet_name: String,
551    pub table_name: Option<String>,
552    pub headers: Vec<String>,
553    pub column_types: Vec<ColumnTypeSummary>,
554    pub row_count: u32,
555    pub samples: Vec<TableRow>,
556    pub notes: Vec<String>,
557}
558
559#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
560pub struct RangeValuesResponse {
561    pub workbook_id: WorkbookId,
562    pub workbook_short_id: String,
563    pub sheet_name: String,
564    pub values: Vec<RangeValuesEntry>,
565}
566
567#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
568pub struct RangeValuesEntry {
569    pub range: String,
570    pub rows: Vec<Vec<Option<CellValue>>>,
571}
572
573#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
574pub struct CloseWorkbookResponse {
575    pub workbook_id: WorkbookId,
576    pub message: String,
577}