docaroo_rs/
pricing.rs

1//! Pricing API operations for in-network contracted rates
2
3use crate::{
4    client::DocarooClient,
5    error::Result,
6    models::{PricingRequest, PricingResponse},
7};
8
9/// Client for pricing-related operations
10#[derive(Debug, Clone)]
11pub struct PricingClient {
12    client: DocarooClient,
13}
14
15impl PricingClient {
16    /// Create a new pricing client
17    pub(crate) fn new(client: DocarooClient) -> Self {
18        Self { client }
19    }
20
21    /// Get in-network contracted rates for healthcare providers
22    ///
23    /// Retrieve contracted rates for healthcare providers (NPIs) for specific billing codes
24    /// and insurance plans. This endpoint supports bulk lookups for up to 10 NPIs per request.
25    ///
26    /// # Arguments
27    ///
28    /// * `request` - The pricing request containing NPIs, billing code, and optional plan ID
29    ///
30    /// # Returns
31    ///
32    /// A `PricingResponse` containing rate data organized by NPI and response metadata
33    ///
34    /// # Errors
35    ///
36    /// Returns an error if:
37    /// - The request contains invalid parameters (e.g., invalid NPI format)
38    /// - Authentication fails (invalid API key)
39    /// - Rate limits are exceeded
40    /// - The API returns an error response
41    ///
42    /// # Example
43    ///
44    /// ```no_run
45    /// use docaroo_rs::{DocarooClient, models::PricingRequest};
46    ///
47    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
48    /// let client = DocarooClient::new("your-api-key");
49    /// 
50    /// let request = PricingRequest::builder()
51    ///     .npis(vec!["1043566623".to_string(), "1972767655".to_string()])
52    ///     .condition_code("99214")
53    ///     .plan_id("942404110")
54    ///     .build();
55    ///
56    /// let response = client.pricing().get_in_network_rates(request).await?;
57    ///
58    /// // Process the response
59    /// for (npi, rates) in response.data {
60    ///     println!("NPI {}: {} rates found", npi, rates.len());
61    ///     for rate in rates {
62    ///         println!("  - Min: ${:.2}, Max: ${:.2}, Avg: ${:.2}",
63    ///             rate.min_rate, rate.max_rate, rate.avg_rate);
64    ///     }
65    /// }
66    /// # Ok(())
67    /// # }
68    /// ```
69    pub async fn get_in_network_rates(&self, request: PricingRequest) -> Result<PricingResponse> {
70        // Validate request
71        self.validate_pricing_request(&request)?;
72
73        // Build URL
74        let url = self.client.build_url("/pricing/in-network")?;
75
76        // Send request
77        let response = self
78            .client
79            .http_client()
80            .post(url)
81            .json(&request)
82            .send()
83            .await?;
84
85        // Handle response
86        DocarooClient::handle_response(response).await
87    }
88
89    /// Validate a pricing request before sending
90    fn validate_pricing_request(&self, request: &PricingRequest) -> Result<()> {
91        use crate::error::DocarooError;
92
93        // Validate NPIs count
94        if request.npis.is_empty() {
95            return Err(DocarooError::InvalidRequest(
96                "At least one NPI must be provided".to_string(),
97            ));
98        }
99
100        if request.npis.len() > 10 {
101            return Err(DocarooError::InvalidRequest(
102                "Maximum 10 NPIs allowed per request".to_string(),
103            ));
104        }
105
106        // Validate NPI format (10 digits)
107        for npi in &request.npis {
108            if npi.len() != 10 || !npi.chars().all(|c| c.is_ascii_digit()) {
109                return Err(DocarooError::InvalidRequest(format!(
110                    "Invalid NPI format: '{}'. NPIs must be 10-digit numbers",
111                    npi
112                )));
113            }
114        }
115
116        // Validate condition code is not empty
117        if request.condition_code.trim().is_empty() {
118            return Err(DocarooError::InvalidRequest(
119                "Condition code cannot be empty".to_string(),
120            ));
121        }
122
123        Ok(())
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn test_validate_pricing_request_valid() {
133        let client = DocarooClient::new("test-key");
134        let pricing_client = PricingClient::new(client);
135
136        let request = PricingRequest::builder()
137            .npis(vec!["1234567890".to_string()])
138            .condition_code("99214")
139            .build();
140
141        assert!(pricing_client.validate_pricing_request(&request).is_ok());
142    }
143
144    #[test]
145    fn test_validate_pricing_request_empty_npis() {
146        let client = DocarooClient::new("test-key");
147        let pricing_client = PricingClient::new(client);
148
149        let request = PricingRequest {
150            npis: vec![],
151            condition_code: "99214".to_string(),
152            plan_id: None,
153            code_type: None,
154        };
155
156        let result = pricing_client.validate_pricing_request(&request);
157        assert!(result.is_err());
158        assert!(result
159            .unwrap_err()
160            .to_string()
161            .contains("At least one NPI must be provided"));
162    }
163
164    #[test]
165    fn test_validate_pricing_request_too_many_npis() {
166        let client = DocarooClient::new("test-key");
167        let pricing_client = PricingClient::new(client);
168
169        let npis: Vec<String> = (0..11).map(|i| format!("{:010}", i)).collect();
170        let request = PricingRequest {
171            npis,
172            condition_code: "99214".to_string(),
173            plan_id: None,
174            code_type: None,
175        };
176
177        let result = pricing_client.validate_pricing_request(&request);
178        assert!(result.is_err());
179        assert!(result
180            .unwrap_err()
181            .to_string()
182            .contains("Maximum 10 NPIs allowed"));
183    }
184
185    #[test]
186    fn test_validate_pricing_request_invalid_npi_format() {
187        let client = DocarooClient::new("test-key");
188        let pricing_client = PricingClient::new(client);
189
190        let request = PricingRequest::builder()
191            .npis(vec!["123".to_string()]) // Too short
192            .condition_code("99214")
193            .build();
194
195        let result = pricing_client.validate_pricing_request(&request);
196        assert!(result.is_err());
197        assert!(result.unwrap_err().to_string().contains("Invalid NPI format"));
198    }
199}