nhs_number/
lib.rs

1//! # NHS Number
2//!
3//! A National Health Service (NHS) Number is a unique number allocated in a shared
4//! numbering scheme to registered users of the three public health services in
5//! England, Wales, and the Isle of Man.
6//!
7//! The NHS Number is the key to the identification of patients, especially in
8//! delivering safe care across provider organisations, and is required in all new
9//! software deployed within the National Health Service (NHS) organizations.
10//!
11//! References:
12//!
13//! * [National Health Service (NHS)](https://en.wikipedia.org/wiki/National_Health_Service)
14//!
15//! * [NHS Number](https://en.wikipedia.org/wiki/NHS_number)
16//!
17//! ## Syntax
18//!
19//! The current system uses a ten-digit number in '3 3 4' format with the final
20//! digit being an error-detecting checksum. Examples given include 987 654 4321.
21//!
22//! ## Ranges
23//!
24//! Currently issued numbers are in these ranges:
25//!
26//! * 300 000 000 to 399 999 999 (England)
27//!
28//! * 400 000 000 to 499 999 999 (England, Wales, Isle of Man)
29//!
30//! * 600 000 000 to 799 999 999 (England, Wales, Isle of Man)
31//!
32//! Unavailable number ranges include:
33//!
34//! * 320 000 001 to 399 999 999 (allocated to the Northern Irish system)
35//!
36//! * 010 100 0000 to 311 299 9999 (used for CHI numbers in Scotland)
37//!
38//! For test purposes, this range is valid but is guaranteed to never be issued:
39//!
40//! * 999 000 0000 to 999 999 9999
41//!
42//! ## Checksum
43//!
44//! The checksum is calculated by multiplying each of the first nine digits by 11
45//! minus its position. Using the number 943 476 5919 as an example:
46//!
47//! * The first digit is 9. This is multiplied by 10.
48//!
49//! * The second digit is 4. This is multiplied by 9.
50//!
51//! * And so on until the ninth digit (1) is multiplied by 2.
52//!
53//! * The result of this calculation is summed. In this example: (9×10) + (4×9) +
54//!   (3×8) + (4×7) + (7×6) + (6×5) + (5×4) + (9×3) + (1×2) = 299.
55//!
56//! * The remainder when dividing this number by 11 is calculated, yielding a number
57//!   in the range 0–10, which would be 2 in this case.
58//!
59//! * Finally, this number is subtracted from 11 to give the checksum in the range
60//!   1–11, in this case 9, which becomes the last digit of the NHS Number.
61//!
62//! * A checksum of 11 is represented by 0 in the final NHS Number. If the checksum
63//!   is 10 then the number is not valid.
64//!
65//! ## Examples
66//!
67//! ```rust
68//! use nhs_number::*;
69//! use std::str::FromStr;
70//!
71//! // Create a new NHS Number with the provided digits.
72//! let nhs_number = NHSNumber { digits: [9, 9, 9, 1, 2, 3, 4, 5, 6, 0] };
73//!
74//! // Create a new NHS Number by converting from a string.
75//! let nhs_number = NHSNumber::from_str("999 123 4560").unwrap();
76//!
77//! // Create a new NHS Number by parsing a string.
78//! let nhs_number: NHSNumber = "999 123 4560".parse().unwrap();
79//!
80//! // Validate a NHS Number using the NHS check digit algorithm.
81//! let valid: bool = nhs_number.validate_check_digit();
82//!
83//! // Create a new NHS Number random sample in the testable range.
84//! let sample = NHSNumber::testable_random_sample();
85//! ```
86//!
87
88use std::fmt;
89use serde::{Serialize, Deserialize};
90
91mod from_str;
92mod parse_error;
93mod testable;
94use testable::*;
95
96/// NHS Number is a unique identifier for patients in the National Health
97/// Service of England, Wales, and the Isle of Man.
98///
99/// Reference:
100///
101/// * [National Health Service (NHS)](https://en.wikipedia.org/wiki/National_Health_Service)
102///
103/// * [NHS Number](https://en.wikipedia.org/wiki/NHS_number)
104///
105#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Serialize, Deserialize)]
106pub struct NHSNumber {
107    pub digits: [i8; 10],
108}
109
110impl NHSNumber {
111
112    /// Create a new NHS Number instance with the provided digits.
113    #[allow(dead_code)]
114    pub fn new(digits: [i8; 10]) -> Self {
115        NHSNumber { digits }
116    }
117
118    // Get the NHS Number check digit i.e. the last digit.
119    #[allow(dead_code)]
120    pub fn check_digit(&self) -> i8 {
121        crate::check_digit(self.digits)
122    }
123
124    // Calculate the NHS Number check digit using a checksum algorithm.
125    #[allow(dead_code)]
126    pub fn calculate_check_digit(&self) -> i8 {
127        crate::calculate_check_digit(self.digits)
128    }
129
130    /// Validate the NHS Number check digit equals the calculated check digit.
131    #[allow(dead_code)]
132    pub fn validate_check_digit(&self) -> bool {
133        crate::check_digit(self.digits) == crate::calculate_check_digit(self.digits)
134    }
135
136    /// Validate the NHS Number check digit equals the calculated check digit.
137    #[allow(dead_code)]
138    pub fn testable_random_sample() -> NHSNumber {
139        crate::testable_random_sample()
140    }
141
142
143}
144
145/// Format the NHS Number is a 10-digit number with spaces:
146///
147/// * 3 digits
148/// * space
149/// * 3 digits
150/// * space
151/// * 4 digits
152///
153impl fmt::Display for NHSNumber {
154    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
155        write!(f, "{}{}{} {}{}{} {}{}{}{}",
156            self.digits[0],
157            self.digits[1],
158            self.digits[2],
159            self.digits[3],
160            self.digits[4],
161            self.digits[5],
162            self.digits[6],
163            self.digits[7],
164            self.digits[8],
165            self.digits[9],
166        )
167    }
168}
169
170//// Functional utilities
171
172/// Format the NHS Number is a 10-digit number with spaces:
173///
174/// * 3 digits
175/// * space
176/// * 3 digits
177/// * space
178/// * 4 digits
179///
180#[allow(dead_code)]
181pub fn format(digits: [i8; 10]) -> String {
182    format!(
183        "{}{}{} {}{}{} {}{}{}{}",
184        digits[0],
185        digits[1],
186        digits[2],
187        digits[3],
188        digits[4],
189        digits[5],
190        digits[6],
191        digits[7],
192        digits[8],
193        digits[9],
194    )
195}
196
197/// Get the NHS Number check digit i.e. the last digit.
198#[allow(dead_code)]
199pub fn check_digit(digits: [i8; 10]) -> i8 {
200    digits[9]
201}
202
203/// Calculate the NHS Number check digit using a checksum algorithm.
204#[allow(dead_code)]
205fn calculate_check_digit(digits: [i8; 10]) -> i8 {
206    let sum: usize = digits
207    .iter()
208    .take(9)
209    .enumerate()
210    .map(|(i, &d)|
211        d as usize * (10 - i as usize)
212    ).sum();
213    ((11 - (sum % 11)) % 10) as i8
214}
215
216#[cfg(test)]
217mod tests {
218
219    mod structure {
220        use super::super::*;
221
222        #[test]
223        fn test_new() {
224            let a: NHSNumber = NHSNumber::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
225            let actual = a.to_string();
226            let expect = "012 345 6789";
227            assert_eq!(actual, expect);
228        }
229
230        #[test]
231        fn test_display() {
232            let a: NHSNumber = NHSNumber::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
233            let actual = a.to_string();
234            let expect = "012 345 6789";
235            assert_eq!(actual, expect);
236        }
237
238        #[test]
239        fn test_partial_eq() {
240            {
241                let a: NHSNumber = NHSNumber::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
242                let b: NHSNumber = NHSNumber::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
243                assert_eq!(a, b);
244            }
245            {
246                let a: NHSNumber = NHSNumber::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
247                let b: NHSNumber = NHSNumber::new([9, 8, 7, 6, 5, 4, 3, 2, 1, 0]);
248                assert_ne!(a, b);
249            }
250        }
251
252        #[test]
253        fn test_check_digit() {
254            let a = NHSNumber::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
255            let actual: i8 = a.check_digit();
256            let expect: i8 = 9;
257            assert_eq!(actual, expect);
258        }
259
260        #[test]
261        fn test_calculate_check_digit() {
262            let a: NHSNumber = NHSNumber::new([9, 9, 9, 1, 2, 3, 4, 5, 6, 0]);
263            let actual: i8 = a.calculate_check_digit();
264            let expect: i8 = 0;
265            assert_eq!(actual, expect);
266        }
267
268        #[test]
269        fn test_validate_check_digit() {
270            {
271                let a: NHSNumber = NHSNumber::new([9, 9, 9, 1, 2, 3, 4, 5, 6, 0]);
272                assert_eq!(a.validate_check_digit(), true);
273            }
274            {
275                let a: NHSNumber = NHSNumber::new([9, 9, 9, 1, 2, 3, 4, 5, 6, 1]);
276                assert_eq!(a.validate_check_digit(), false);
277            }
278        }
279
280        #[test]
281        fn test_testable_random_sample() {
282            let a: NHSNumber = NHSNumber::testable_random_sample();
283            assert!(a >= *crate::testable::TESTABLE_MIN);
284            assert!(a <= *crate::testable::TESTABLE_MAX);
285        }
286
287    }
288
289    mod utilities {
290
291        #[test]
292        fn test_format() {
293            let digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
294            let actual = crate::format(digits);
295            let expect = "012 345 6789";
296            assert_eq!(actual, expect);
297        }
298   
299        #[test]
300        fn test_check_digit() {
301            let digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
302            let actual: i8 = crate::check_digit(digits);
303            let expect: i8 = 9;
304            assert_eq!(actual, expect);
305        }
306
307        #[test]
308        fn test_calculate_check_digit() {
309            let digits = [9, 9, 9, 1, 2, 3, 4, 5, 6, 0];
310            let actual: i8 = crate::calculate_check_digit(digits);
311            let expect: i8 = 0;
312            assert_eq!(actual, expect);
313        }
314
315    }
316
317}