docaroo_rs/procedures.rs
1//! Procedures API operations for likelihood scoring
2
3use crate::{
4 client::DocarooClient,
5 error::Result,
6 models::{LikelihoodRequest, LikelihoodResponse},
7};
8
9/// Client for procedure likelihood operations
10#[derive(Debug, Clone)]
11pub struct ProceduresClient {
12 client: DocarooClient,
13}
14
15impl ProceduresClient {
16 /// Create a new procedures client
17 pub(crate) fn new(client: DocarooClient) -> Self {
18 Self { client }
19 }
20
21 /// Get procedure likelihood scores for healthcare providers
22 ///
23 /// Evaluate the likelihood that healthcare providers (NPIs) perform specific medical
24 /// procedures or services. The API analyzes historical claims data and provider
25 /// specialties to generate confidence scores from 0.0 (unlikely) to 1.0 (highly likely).
26 ///
27 /// # Arguments
28 ///
29 /// * `request` - The likelihood request containing NPIs, billing code, and code type
30 ///
31 /// # Returns
32 ///
33 /// A `LikelihoodResponse` containing likelihood scores organized by NPI and metadata
34 ///
35 /// # Errors
36 ///
37 /// Returns an error if:
38 /// - The request contains invalid parameters
39 /// - Authentication fails (invalid API key)
40 /// - Rate limits are exceeded
41 /// - The API returns an error response
42 ///
43 /// # Example
44 ///
45 /// ```no_run
46 /// use docaroo_rs::{DocarooClient, models::LikelihoodRequest};
47 ///
48 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
49 /// let client = DocarooClient::new("your-api-key");
50 ///
51 /// let request = LikelihoodRequest::builder()
52 /// .npis(vec!["1487648176".to_string()])
53 /// .condition_code("99214")
54 /// .code_type("CPT")
55 /// .build();
56 ///
57 /// let response = client.procedures().get_likelihood(request).await?;
58 ///
59 /// // Process the response
60 /// for (npi, data) in response.data {
61 /// println!("NPI {}: Likelihood score = {:.2}", npi, data.likelihood);
62 /// }
63 /// # Ok(())
64 /// # }
65 /// ```
66 pub async fn get_likelihood(&self, request: LikelihoodRequest) -> Result<LikelihoodResponse> {
67 // Validate request
68 self.validate_likelihood_request(&request)?;
69
70 // Build URL
71 let url = self.client.build_url("/procedures/likelihood")?;
72
73 // Send request
74 let response = self
75 .client
76 .http_client()
77 .post(url)
78 .json(&request)
79 .send()
80 .await?;
81
82 // Handle response
83 DocarooClient::handle_response(response).await
84 }
85
86 /// Validate a likelihood request before sending
87 fn validate_likelihood_request(&self, request: &LikelihoodRequest) -> Result<()> {
88 use crate::error::DocarooError;
89
90 // Validate NPIs
91 if request.npis.is_empty() {
92 return Err(DocarooError::InvalidRequest(
93 "At least one NPI must be provided".to_string(),
94 ));
95 }
96
97 // Validate NPI format (10 digits)
98 for npi in &request.npis {
99 if npi.len() != 10 || !npi.chars().all(|c| c.is_ascii_digit()) {
100 return Err(DocarooError::InvalidRequest(format!(
101 "Invalid NPI format: '{}'. NPIs must be 10-digit numbers",
102 npi
103 )));
104 }
105 }
106
107 // Validate condition code is not empty
108 if request.condition_code.trim().is_empty() {
109 return Err(DocarooError::InvalidRequest(
110 "Condition code cannot be empty".to_string(),
111 ));
112 }
113
114 // Validate code type is not empty
115 if request.code_type.trim().is_empty() {
116 return Err(DocarooError::InvalidRequest(
117 "Code type cannot be empty".to_string(),
118 ));
119 }
120
121 Ok(())
122 }
123
124 /// Check multiple providers for a procedure at once
125 ///
126 /// This is a convenience method that allows checking multiple providers
127 /// for the same procedure in a single request.
128 ///
129 /// # Arguments
130 ///
131 /// * `npis` - List of National Provider Identifiers
132 /// * `condition_code` - Medical billing code
133 /// * `code_type` - Medical billing code standard (e.g., "CPT")
134 ///
135 /// # Example
136 ///
137 /// ```no_run
138 /// # use docaroo_rs::DocarooClient;
139 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
140 /// let client = DocarooClient::new("your-api-key");
141 ///
142 /// let npis = vec!["1487648176", "1234567890"];
143 /// let response = client.procedures()
144 /// .check_providers(&npis, "99214", "CPT")
145 /// .await?;
146 /// # Ok(())
147 /// # }
148 /// ```
149 pub async fn check_providers(
150 &self,
151 npis: &[&str],
152 condition_code: impl Into<String>,
153 code_type: impl Into<String>,
154 ) -> Result<LikelihoodResponse> {
155 let request = LikelihoodRequest::builder()
156 .npis(npis.iter().map(|&s| s.to_string()).collect::<Vec<_>>())
157 .condition_code(condition_code)
158 .code_type(code_type)
159 .build();
160
161 self.get_likelihood(request).await
162 }
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 #[test]
170 fn test_validate_likelihood_request_valid() {
171 let client = DocarooClient::new("test-key");
172 let procedures_client = ProceduresClient::new(client);
173
174 let request = LikelihoodRequest::builder()
175 .npis(vec![String::from("1234567890")])
176 .condition_code("99214")
177 .code_type("CPT")
178 .build();
179
180 assert!(procedures_client.validate_likelihood_request(&request).is_ok());
181 }
182
183 #[test]
184 fn test_validate_likelihood_request_empty_npis() {
185 let client = DocarooClient::new("test-key");
186 let procedures_client = ProceduresClient::new(client);
187
188 let request = LikelihoodRequest {
189 npis: vec![],
190 condition_code: "99214".to_string(),
191 code_type: "CPT".to_string(),
192 };
193
194 let result = procedures_client.validate_likelihood_request(&request);
195 assert!(result.is_err());
196 assert!(result
197 .unwrap_err()
198 .to_string()
199 .contains("At least one NPI must be provided"));
200 }
201
202 #[test]
203 fn test_validate_likelihood_request_invalid_npi() {
204 let client = DocarooClient::new("test-key");
205 let procedures_client = ProceduresClient::new(client);
206
207 let request = LikelihoodRequest::builder()
208 .npis(vec![String::from("ABC1234567")]) // Contains letters
209 .condition_code("99214")
210 .code_type("CPT")
211 .build();
212
213 let result = procedures_client.validate_likelihood_request(&request);
214 assert!(result.is_err());
215 assert!(result.unwrap_err().to_string().contains("Invalid NPI format"));
216 }
217
218 #[test]
219 fn test_validate_likelihood_request_empty_code_type() {
220 let client = DocarooClient::new("test-key");
221 let procedures_client = ProceduresClient::new(client);
222
223 let request = LikelihoodRequest {
224 npis: vec!["1234567890".to_string()],
225 condition_code: "99214".to_string(),
226 code_type: "".to_string(),
227 };
228
229 let result = procedures_client.validate_likelihood_request(&request);
230 assert!(result.is_err());
231 assert!(result
232 .unwrap_err()
233 .to_string()
234 .contains("Code type cannot be empty"));
235 }
236}