Commits
thomas@kvalitetsit.dk authored 64b2af51ab5
1 + | /** |
2 + | * The MIT License |
3 + | * |
4 + | * Original work sponsored and donated by National Board of e-Health (NSI), Denmark |
5 + | * (http://www.nsi.dk) |
6 + | * |
7 + | * Copyright (C) 2016 National Board of e-Health (NSI), Denmark (http://www.nsi.dk) |
8 + | * |
9 + | * Permission is hereby granted, free of charge, to any person obtaining a copy of |
10 + | * this software and associated documentation files (the "Software"), to deal in |
11 + | * the Software without restriction, including without limitation the rights to |
12 + | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies |
13 + | * of the Software, and to permit persons to whom the Software is furnished to do |
14 + | * so, subject to the following conditions: |
15 + | * |
16 + | * The above copyright notice and this permission notice shall be included in all |
17 + | * copies or substantial portions of the Software. |
18 + | * |
19 + | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
20 + | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
21 + | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
22 + | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
23 + | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
24 + | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
25 + | * SOFTWARE. |
26 + | */ |
27 + | package dk.nsi.dds.projects.sxa.documentprovider.document; |
28 + | |
29 + | import java.util.regex.Pattern; |
30 + | |
31 + | import org.joda.time.DateTime; |
32 + | import org.joda.time.DateTimeZone; |
33 + | |
34 + | import dk.nsi.dds.projects.sxa.common.InvalidDateException; |
35 + | import dk.nsi.dds.projects.sxa.common.PatientIdDerivationException; |
36 + | |
37 + | /** |
38 + | * Holds an instance of a patient id (CPR and eCPR). |
39 + | */ |
40 + | public class PatientId { |
41 + | private static final String DEFAULT_BIRTHDAY_FORMAT = "ddMMYYYY"; |
42 + | private static final Pattern PATIENT_ID_PATTERN = Pattern.compile("^(\\d){10}$"); |
43 + | public static final int NORMAL_DAY_RANGE_START = 1; |
44 + | public static final int NORMAL_DAY_RANGE_END = 31; |
45 + | public static final int ALTERNATIVE_DAY_RANGE_START = 61; |
46 + | public static final int ALTERNATIVE_DAY_RANGE_END = 91; |
47 + | public static final int YEAR_RANGE_START = 0; |
48 + | public static final int YEAR_RANGE_END = 99; |
49 + | public static final int MONTH_RANGE_START = 1; |
50 + | public static final int MONTH_RANGE_END = 12; |
51 + | public static final char GENDER_FEMALE = 'F'; |
52 + | public static final char GENDER_MALE = 'M'; |
53 + | private int day; |
54 + | private int month; |
55 + | private int year; |
56 + | private int controlCiffer; |
57 + | private boolean isAlternative; |
58 + | private String rawId; |
59 + | |
60 + | private PatientId(int day, int month, int year, int controlCiffer, boolean isAlternative, String rawId) { |
61 + | super(); |
62 + | this.day = day; |
63 + | this.month = month; |
64 + | this.year = year; |
65 + | this.controlCiffer = controlCiffer; |
66 + | this.isAlternative = isAlternative; |
67 + | this.rawId = rawId; |
68 + | } |
69 + | |
70 + | public static PatientId createPatientId(String rawPatientId) throws PatientIdDerivationException { |
71 + | if (rawPatientId == null) { |
72 + | throw new PatientIdDerivationException("PatientId is null"); |
73 + | } |
74 + | rawPatientId = rawPatientId.trim(); |
75 + | if (!PATIENT_ID_PATTERN.matcher(rawPatientId).matches()) { |
76 + | throw new PatientIdDerivationException("PatientId format is invalid"); |
77 + | } |
78 + | boolean isAlternative = isAlternative(rawPatientId); |
79 + | assertValidPatientDate(rawPatientId, isAlternative); |
80 + | return new PatientId(parseSubstring(rawPatientId, 0, 2), parseSubstring(rawPatientId, 2, 4), |
81 + | parseSubstring(rawPatientId, 4, 6), parseSubstring(rawPatientId, 6, 10), isAlternative, rawPatientId); |
82 + | } |
83 + | |
84 + | private static int parseSubstring(String text, int start, int end) { |
85 + | return Integer.parseInt(text.substring(start, end)); |
86 + | } |
87 + | |
88 + | private static boolean isAlternative(String rawPatientId) throws PatientIdDerivationException { |
89 + | int day = Integer.parseInt(rawPatientId.substring(0, 2)); |
90 + | if (day < NORMAL_DAY_RANGE_START || (day > NORMAL_DAY_RANGE_END && day < ALTERNATIVE_DAY_RANGE_START) |
91 + | || day > ALTERNATIVE_DAY_RANGE_END) { |
92 + | throw new PatientIdDerivationException("Day is invalid: " + day); |
93 + | } |
94 + | return day > NORMAL_DAY_RANGE_END; |
95 + | } |
96 + | |
97 + | /** |
98 + | * Verifies that given patient id has CPR number form and contains date information |
99 + | * which is sensible. The method must not be used as validation whether the patient |
100 + | * id identifies a real person or not. |
101 + | * |
102 + | * @param patientId |
103 + | * @throws PatientIdDerivationException |
104 + | */ |
105 + | private static void assertValidPatientDate(String patientId, boolean isAlternative) |
106 + | throws PatientIdDerivationException { |
107 + | int day = Integer.parseInt(patientId.substring(0, 2)); |
108 + | int month = Integer.parseInt(patientId.substring(2, 4)); |
109 + | Integer.parseInt(patientId.substring(4, 6)); // year sensible? |
110 + | if (isAlternative) { |
111 + | day = day - ALTERNATIVE_DAY_RANGE_START + 1; |
112 + | } |
113 + | if (day < NORMAL_DAY_RANGE_START || day > NORMAL_DAY_RANGE_END || month < MONTH_RANGE_START |
114 + | || month > MONTH_RANGE_END) { |
115 + | throw new PatientIdDerivationException("Day and/or month not of sensible values in patient id"); |
116 + | } |
117 + | } |
118 + | |
119 + | public char determineGender() throws PatientIdDerivationException { |
120 + | if (controlCiffer % 2 == 0) { |
121 + | return GENDER_FEMALE; |
122 + | } |
123 + | return GENDER_MALE; |
124 + | } |
125 + | |
126 + | /** |
127 + | * Calculates person birthday from person id. Algorithm for determining birth year |
128 + | * takes two first digits from current year, adds two digits from person id, and |
129 + | * compares the resulting year with current year. If resulting year is greater than |
130 + | * current year, 100 years are subtracted from the resulting year. |
131 + | * |
132 + | * Example: CurrentYear: 2015 PersonId: 010116 Output: 1916 |
133 + | * |
134 + | * CurrentYear: 2015 personId: 010115 Output: 2015 |
135 + | * |
136 + | * @param patientId |
137 + | * @return |
138 + | * @throws InvalidDateException |
139 + | */ |
140 + | public DateTime deriveDateOfBirth() throws InvalidDateException { |
141 + | try { |
142 + | DateTime now = new DateTime(DateTimeZone.UTC); |
143 + | int currentYear = now.getYear(); |
144 + | String currentYearText = currentYear + ""; |
145 + | int proposedYear = Integer |
146 + | .parseInt(currentYearText.substring(0, currentYearText.length() - 2) + String.format("%02d", year)); |
147 + | int correctedDay = isAlternative ? day - ALTERNATIVE_DAY_RANGE_START + 1 : day; |
148 + | DateTime result = new DateTime(proposedYear, month, correctedDay, 0, 0, DateTimeZone.UTC); |
149 + | if (result.isAfter(now)) { |
150 + | result = result.minusYears(100); |
151 + | } |
152 + | return result; |
153 + | } catch (Exception ex) { |
154 + | throw new InvalidDateException(ex); |
155 + | } |
156 + | } |
157 + | |
158 + | public String determineDateofBirthText() throws InvalidDateException { |
159 + | return determineDateofBirthText(DEFAULT_BIRTHDAY_FORMAT); |
160 + | } |
161 + | |
162 + | public String determineDateofBirthText(String format) throws InvalidDateException { |
163 + | return deriveDateOfBirth().toString(format); |
164 + | } |
165 + | |
166 + | public boolean isAlternative() { |
167 + | return isAlternative; |
168 + | } |
169 + | |
170 + | public String getRawId() { |
171 + | return rawId; |
172 + | } |
173 + | |
174 + | /** |
175 + | * Converts year to a two digit string e.g: 1980 -> 80 |
176 + | * |
177 + | * @param year Complete year as a number |
178 + | * @return Truncated year as String |
179 + | */ |
180 + | private static String yearToCprText(int year) { |
181 + | String textYear = Integer.toString(year); |
182 + | if (textYear.length() > 2) { |
183 + | return textYear.substring(textYear.length() - 2, textYear.length()); |
184 + | } |
185 + | return textYear; |
186 + | } |
187 + | } |