docaroo_rs/
models.rs

1//! Data models for the Docaroo API
2
3use bon::Builder;
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8/// Medical billing code types supported by the API
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "SCREAMING-KEBAB-CASE")]
11pub enum CodeType {
12    /// Current Procedural Terminology
13    #[serde(rename = "CPT")]
14    Cpt,
15    /// National Drug Code
16    #[serde(rename = "NDC")]
17    Ndc,
18    /// Healthcare Common Procedure Coding System
19    #[serde(rename = "HCPCS")]
20    Hcpcs,
21    /// Revenue Code
22    #[serde(rename = "RC")]
23    Rc,
24    /// International Classification of Diseases
25    #[serde(rename = "ICD")]
26    Icd,
27    /// Medicare Severity Diagnosis Related Group
28    #[serde(rename = "MS-DRG")]
29    MsDrg,
30    /// Refined Diagnosis Related Group
31    #[serde(rename = "R-DRG")]
32    RDrg,
33    /// Severity Diagnosis Related Group
34    #[serde(rename = "S-DRG")]
35    SDrg,
36    /// All Patient Severity Diagnosis Related Group
37    #[serde(rename = "APS-DRG")]
38    ApsDrg,
39    /// All Patient Diagnosis Related Group
40    #[serde(rename = "AP-DRG")]
41    ApDrg,
42    /// All Patient Refined Diagnosis Related Group
43    #[serde(rename = "APR-DRG")]
44    AprDrg,
45    /// Ambulatory Payment Classification
46    #[serde(rename = "APC")]
47    Apc,
48    /// Local code
49    #[serde(rename = "LOCAL")]
50    Local,
51    /// Enhanced Ambulatory Patient Grouping
52    #[serde(rename = "EAPG")]
53    Eapg,
54    /// Health Insurance Prospective Payment System
55    #[serde(rename = "HIPPS")]
56    Hipps,
57    /// Current Dental Terminology
58    #[serde(rename = "CDT")]
59    Cdt,
60    /// Custom All
61    #[serde(rename = "CSTM-ALL")]
62    CstmAll,
63}
64
65impl Default for CodeType {
66    fn default() -> Self {
67        Self::Cpt
68    }
69}
70
71/// Request for in-network pricing lookup
72#[derive(Debug, Clone, Serialize, Builder)]
73#[serde(rename_all = "camelCase")]
74pub struct PricingRequest {
75    /// List of National Provider Identifiers (NPIs) to lookup pricing for
76    /// Must be 10-digit identifiers, 1-10 items allowed
77    #[builder(into)]
78    pub npis: Vec<String>,
79    
80    /// Medical billing code to retrieve pricing for
81    #[builder(into)]
82    pub condition_code: String,
83    
84    /// Insurance plan identifier (EIN, HIOS ID, or Custom Plan ID)
85    #[serde(skip_serializing_if = "Option::is_none")]
86    #[builder(into)]
87    pub plan_id: Option<String>,
88    
89    /// Medical billing code standard
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub code_type: Option<CodeType>,
92}
93
94/// Request for procedure likelihood evaluation
95#[derive(Debug, Clone, Serialize, Builder)]
96#[serde(rename_all = "camelCase")]
97pub struct LikelihoodRequest {
98    /// List of National Provider Identifiers (NPIs) to evaluate
99    #[builder(into)]
100    pub npis: Vec<String>,
101    
102    /// Medical billing code to evaluate likelihood for
103    #[builder(into)]
104    pub condition_code: String,
105    
106    /// Medical billing code standard
107    #[builder(into)]
108    pub code_type: String,
109}
110
111/// Response containing pricing data
112#[derive(Debug, Clone, Deserialize)]
113pub struct PricingResponse {
114    /// Pricing data organized by NPI
115    pub data: HashMap<String, Vec<RateData>>,
116    /// Response metadata
117    pub meta: PricingMeta,
118}
119
120/// Response containing likelihood scores
121#[derive(Debug, Clone, Deserialize)]
122pub struct LikelihoodResponse {
123    /// Likelihood scores organized by NPI
124    pub data: HashMap<String, LikelihoodData>,
125    /// Response metadata
126    pub meta: LikelihoodMeta,
127}
128
129/// Rate data for a specific billing code
130#[derive(Debug, Clone, Deserialize)]
131#[serde(rename_all = "camelCase")]
132pub struct RateData {
133    /// Medical billing code
134    pub code: String,
135    /// Medical billing code standard
136    pub code_type: String,
137    /// Type of negotiated rate
138    pub negotiated_type: String,
139    /// Minimum contracted rate
140    pub min_rate: f64,
141    /// Maximum contracted rate
142    pub max_rate: f64,
143    /// Average contracted rate
144    pub avg_rate: f64,
145    /// Number of rate instances found
146    pub instances: u32,
147}
148
149/// Likelihood data for a specific billing code
150#[derive(Debug, Clone, Deserialize)]
151#[serde(rename_all = "camelCase")]
152pub struct LikelihoodData {
153    /// Medical billing code
154    pub code: String,
155    /// Medical billing code standard
156    pub code_type: String,
157    /// Likelihood score from 0.0 (unlikely) to 1.0 (highly likely)
158    pub likelihood: f64,
159}
160
161/// Metadata for pricing responses
162#[derive(Debug, Clone, Deserialize)]
163#[serde(rename_all = "camelCase")]
164pub struct PricingMeta {
165    /// Insurance plan identifier
166    pub plan_id: String,
167    /// Insurance payer code
168    pub payer: String,
169    /// Unique request identifier
170    pub request_id: String,
171    /// Request timestamp in ISO 8601 format
172    pub timestamp: DateTime<Utc>,
173    /// Processing time in milliseconds
174    pub processing_time_ms: u32,
175    /// Number of in-network records found
176    pub in_network_records_count: u32,
177}
178
179/// Metadata for likelihood responses
180#[derive(Debug, Clone, Deserialize)]
181#[serde(rename_all = "camelCase")]
182pub struct LikelihoodMeta {
183    /// Unique request identifier
184    pub request_id: String,
185    /// Request timestamp in ISO 8601 format
186    pub timestamp: DateTime<Utc>,
187    /// Processing time in milliseconds
188    pub processing_time_ms: u32,
189    /// Number of out-of-network records analyzed
190    pub out_of_network_records_count: u32,
191}
192
193/// Error response from the API
194#[derive(Debug, Clone, Deserialize)]
195#[serde(rename_all = "camelCase")]
196pub struct ErrorResponse {
197    /// Error type
198    pub error: String,
199    /// Human-readable error message
200    pub message: String,
201    /// Additional error details
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub details: Option<serde_json::Value>,
204    /// Request identifier for support
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub request_id: Option<String>,
207    /// Error timestamp
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub timestamp: Option<DateTime<Utc>>,
210}
211
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216
217    #[test]
218    fn test_pricing_request_builder() {
219        let request = PricingRequest::builder()
220            .npis(vec![String::from("1043566623"), String::from("1972767655")])
221            .condition_code("99214")
222            .plan_id("942404110")
223            .code_type(CodeType::Cpt)
224            .build();
225
226        assert_eq!(request.npis.len(), 2);
227        assert_eq!(request.condition_code, "99214");
228        assert_eq!(request.plan_id, Some("942404110".to_string()));
229        assert_eq!(request.code_type, Some(CodeType::Cpt));
230    }
231
232    #[test]
233    fn test_likelihood_request_builder() {
234        let request = LikelihoodRequest::builder()
235            .npis(vec!["1487648176".to_string()])
236            .condition_code("99214")
237            .code_type("CPT")
238            .build();
239
240        assert_eq!(request.npis.len(), 1);
241        assert_eq!(request.condition_code, "99214");
242        assert_eq!(request.code_type, "CPT");
243    }
244
245    #[test]
246    fn test_code_type_serialization() {
247        let code_type = CodeType::Cpt;
248        let json = serde_json::to_string(&code_type).unwrap();
249        assert_eq!(json, r#""CPT""#);
250
251        let deserialized: CodeType = serde_json::from_str(&json).unwrap();
252        assert_eq!(deserialized, CodeType::Cpt);
253    }
254}