1use serde::{Deserialize, Serialize};
24
25use crate::grounding::{Capabilities, ParserIdentity};
26use crate::verify_types::CapabilityLimit;
27
28pub const EVIDENCE_ANCHOR_REQUEST_ARTIFACT_TYPE: &str = "ethos.evidence_anchor_request.v1";
30pub const EVIDENCE_ANCHOR_REPORT_ARTIFACT_TYPE: &str = "ethos.evidence_anchor_report.v1";
32
33#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
35#[serde(deny_unknown_fields)]
36pub struct EvidenceAnchorRequest {
37 pub artifact_type: String,
39 pub schema_version: String,
41 #[serde(skip_serializing_if = "Option::is_none")]
43 pub source_fingerprint: Option<String>,
44 pub evidence_refs: Vec<EvidenceRef>,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
50#[serde(deny_unknown_fields)]
51pub struct EvidenceRef {
52 pub evidence_id: String,
54 pub evidence_kind: EvidenceKind,
56 pub required_anchor_level: AnchorLevel,
58 pub locator: EvidenceLocator,
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub expected_text: Option<String>,
63 #[serde(skip_serializing_if = "Option::is_none")]
65 pub expected_text_sha256: Option<String>,
66 #[serde(skip_serializing_if = "Option::is_none")]
68 pub text_normalization_profile: Option<TextNormalizationProfile>,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
73#[serde(rename_all = "snake_case")]
74pub enum EvidenceKind {
75 Page,
77 Text,
79 TextRegion,
81 TableCell,
83 Region,
85 Other,
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
91#[serde(rename_all = "snake_case")]
92pub enum AnchorLevel {
93 None,
95 Page,
97 Text,
99 Bbox,
101 TextBbox,
103 TableCell,
105}
106
107#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
109#[serde(deny_unknown_fields)]
110pub struct EvidenceLocator {
111 #[serde(skip_serializing_if = "Option::is_none")]
113 pub page_index: Option<u32>,
114 #[serde(skip_serializing_if = "Option::is_none")]
116 pub page_id: Option<String>,
117 #[serde(skip_serializing_if = "Option::is_none")]
119 pub element_id: Option<String>,
120 #[serde(skip_serializing_if = "Option::is_none")]
122 pub span_id: Option<String>,
123 #[serde(skip_serializing_if = "Option::is_none")]
125 pub bbox: Option<[i64; 4]>,
126 #[serde(skip_serializing_if = "Option::is_none")]
128 pub table_id: Option<String>,
129 #[serde(skip_serializing_if = "Option::is_none")]
131 pub cell: Option<AnchorCellRef>,
132 #[serde(skip_serializing_if = "Option::is_none")]
134 pub coordinate_profile: Option<CoordinateProfile>,
135}
136
137#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
139#[serde(deny_unknown_fields)]
140pub struct AnchorCellRef {
141 pub row: u32,
143 pub col: u32,
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
149#[serde(rename_all = "snake_case")]
150pub enum TextNormalizationProfile {
151 EthosCollapseWhitespaceV1,
153}
154
155#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
157#[serde(rename_all = "snake_case")]
158pub enum CoordinateProfile {
159 EthosQuantizedTopLeftV1,
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
165#[serde(deny_unknown_fields)]
166pub struct EvidenceAnchorReport {
167 pub artifact_type: String,
169 pub schema_version: String,
171 #[serde(skip_serializing_if = "Option::is_none")]
173 pub source_fingerprint: Option<String>,
174 pub grounding: EvidenceAnchorGrounding,
176 pub anchors: Vec<EvidenceAnchor>,
178}
179
180#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
182#[serde(deny_unknown_fields)]
183pub struct EvidenceAnchorGrounding {
184 pub parser: ParserIdentity,
186 pub capabilities: Capabilities,
188}
189
190#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
192#[serde(deny_unknown_fields)]
193pub struct EvidenceAnchor {
194 pub evidence_id: String,
196 pub evidence_kind: EvidenceKind,
198 pub anchor_status: AnchorStatus,
200 pub required_anchor_level: AnchorLevel,
202 pub achieved_anchor_level: AnchorLevel,
204 pub checks: AnchorChecks,
206 pub capability_limits: Vec<CapabilityLimit>,
208}
209
210#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
212#[serde(rename_all = "snake_case")]
213pub enum AnchorStatus {
214 Bound,
216 Mismatch,
218 NotFound,
220 StaleFingerprint,
222 CapabilityLimited,
224 UnsupportedEvidenceKind,
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
230#[serde(deny_unknown_fields)]
231pub struct AnchorChecks {
232 pub fingerprint: FingerprintCheck,
234 pub page: PageCheck,
236 pub text: TextCheck,
238 pub bbox: BboxCheck,
240 pub table_cell: TableCellCheck,
242}
243
244impl Default for AnchorChecks {
245 fn default() -> Self {
246 AnchorChecks {
247 fingerprint: FingerprintCheck::NotChecked,
248 page: PageCheck::NotChecked,
249 text: TextCheck::NotChecked,
250 bbox: BboxCheck::NotChecked,
251 table_cell: TableCellCheck::NotChecked,
252 }
253 }
254}
255
256#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
258#[serde(rename_all = "snake_case")]
259pub enum FingerprintCheck {
260 Matched,
262 Stale,
264 NotChecked,
266 CapabilityLimited,
268}
269
270#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
272#[serde(rename_all = "snake_case")]
273pub enum PageCheck {
274 Found,
276 NotFound,
278 NotChecked,
280}
281
282#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
284#[serde(rename_all = "snake_case")]
285pub enum TextCheck {
286 Matched,
288 Mismatch,
290 NotFound,
292 NotChecked,
294 CapabilityLimited,
296}
297
298#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
300#[serde(rename_all = "snake_case")]
301pub enum BboxCheck {
302 Valid,
304 Invalid,
306 NotFound,
308 NotChecked,
310 CapabilityLimited,
312}
313
314#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
316#[serde(rename_all = "snake_case")]
317pub enum TableCellCheck {
318 Matched,
320 Mismatch,
322 NotFound,
324 NotChecked,
326 CapabilityLimited,
328}