| | 31 | protected static Log log = LogFactory.getLog(DoubleEntryReconciliationUtil.class); |
|---|
| | 32 | |
|---|
| | 33 | static class ObsAndTag implements Comparable<ObsAndTag> { |
|---|
| | 34 | private Obs obs; |
|---|
| | 35 | private String tag; |
|---|
| | 36 | public ObsAndTag(Obs obs, String tag) { |
|---|
| | 37 | this.obs = obs; |
|---|
| | 38 | this.tag = tag; |
|---|
| | 39 | } |
|---|
| | 40 | public Obs getObs() { |
|---|
| | 41 | return obs; |
|---|
| | 42 | } |
|---|
| | 43 | public void setObs(Obs obs) { |
|---|
| | 44 | this.obs = obs; |
|---|
| | 45 | } |
|---|
| | 46 | public String getTag() { |
|---|
| | 47 | return tag; |
|---|
| | 48 | } |
|---|
| | 49 | public void setTag(String tag) { |
|---|
| | 50 | this.tag = tag; |
|---|
| | 51 | } |
|---|
| | 52 | public boolean equals(ObsAndTag other) { |
|---|
| | 53 | return tag.equals(other.tag); |
|---|
| | 54 | } |
|---|
| | 55 | public int compareTo(ObsAndTag other) { |
|---|
| | 56 | return tag.compareTo(other.tag); |
|---|
| | 57 | } |
|---|
| | 58 | } |
|---|
| | 59 | |
|---|
| | 60 | /** |
|---|
| | 61 | * Takes a collection of encounters belonging to one patient that ought to be equal, and returns any discrepancies. |
|---|
| | 62 | * Currently ignores obs groups |
|---|
| | 63 | * @param encounters |
|---|
| | 64 | * @param questionsToIgnore |
|---|
| | 65 | * @return |
|---|
| | 66 | */ |
|---|
| | 67 | public static List<ReconcileFormError> findDiscrepancies(Collection<Encounter> encounters, Collection<Concept> questionsToIgnore) { |
|---|
| | 68 | List<ReconcileFormError> ret = new ArrayList<ReconcileFormError>(); |
|---|
| | 69 | if (encounters.size() > 1) { |
|---|
| | 70 | List<Encounter> encounterList = new ArrayList<Encounter>(encounters); |
|---|
| | 71 | Encounter oneEncounter = encounters.iterator().next(); |
|---|
| | 72 | Patient patient = oneEncounter.getPatient(); |
|---|
| | 73 | Date date = oneEncounter.getEncounterDatetime(); |
|---|
| | 74 | Locale locale = Context.getLocale(); |
|---|
| | 75 | DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); |
|---|
| | 76 | |
|---|
| | 77 | // list (for each encounter) of a map from concept name to all obs with that concept |
|---|
| | 78 | List<Map<String, List<ObsAndTag>>> work = new ArrayList<Map<String, List<ObsAndTag>>>(); |
|---|
| | 79 | |
|---|
| | 80 | // list (for each encounter) of a map from Set<conceptId> to a list of obs groups |
|---|
| | 81 | List<Map<SortedSet<Integer>, List<List<ObsAndTag>>>> obsGroups = |
|---|
| | 82 | new ArrayList<Map<SortedSet<Integer>, List<List<ObsAndTag>>>>(); |
|---|
| | 83 | |
|---|
| | 84 | for (Encounter e : encounters) { |
|---|
| | 85 | if (e.isVoided()) |
|---|
| | 86 | continue; |
|---|
| | 87 | Map<String, List<ObsAndTag>> data = new TreeMap<String, List<ObsAndTag>>(); |
|---|
| | 88 | Map<Integer, List<ObsAndTag>> groups = new HashMap<Integer, List<ObsAndTag>>(); |
|---|
| | 89 | if (e.getObs() != null) { |
|---|
| | 90 | for (Obs obs : e.getObs()) { |
|---|
| | 91 | if (obs.isVoided()) |
|---|
| | 92 | continue; |
|---|
| | 93 | if (questionsToIgnore != null && questionsToIgnore.contains(obs.getConcept())) |
|---|
| | 94 | continue; |
|---|
| | 95 | if (obs.getValueGroupId() != null) { |
|---|
| | 96 | log.warn("obs with valueGroups not yet implemented"); |
|---|
| | 97 | continue; |
|---|
| | 98 | } |
|---|
| | 99 | if (obs.getObsGroupId() != null) { |
|---|
| | 100 | List<ObsAndTag> thisGroup = groups.get(obs.getObsGroupId()); |
|---|
| | 101 | if (thisGroup == null) { |
|---|
| | 102 | thisGroup = new ArrayList<ObsAndTag>(); |
|---|
| | 103 | groups.put(obs.getObsGroupId(), thisGroup); |
|---|
| | 104 | } |
|---|
| | 105 | thisGroup.add(new ObsAndTag(obs, obs.getValueAsString(locale).trim() + " . " + df.format(obs.getObsDatetime()))); |
|---|
| | 106 | } else { |
|---|
| | 107 | String conceptName = obs.getConcept().getName().getName(); |
|---|
| | 108 | List<ObsAndTag> list = data.get(conceptName); |
|---|
| | 109 | if (list == null) { |
|---|
| | 110 | list = new ArrayList<ObsAndTag>(); |
|---|
| | 111 | data.put(conceptName, list); |
|---|
| | 112 | } |
|---|
| | 113 | list.add(new ObsAndTag(obs, obs.getValueAsString(locale).trim() + " . " + df.format(obs.getObsDatetime()))); |
|---|
| | 114 | } |
|---|
| | 115 | } |
|---|
| | 116 | } |
|---|
| | 117 | |
|---|
| | 118 | // now sort those lists |
|---|
| | 119 | for (List<ObsAndTag> list : data.values()) |
|---|
| | 120 | Collections.sort(list); |
|---|
| | 121 | |
|---|
| | 122 | for (List<ObsAndTag> group : groups.values()) |
|---|
| | 123 | Collections.sort(group, new Comparator<ObsAndTag>() { |
|---|
| | 124 | public int compare(ObsAndTag left, ObsAndTag right) { |
|---|
| | 125 | int temp = left.getObs().getConcept().getConceptId().compareTo(right.getObs().getConcept().getConceptId()); |
|---|
| | 126 | if (temp == 0) |
|---|
| | 127 | temp = left.compareTo(right); |
|---|
| | 128 | return temp; |
|---|
| | 129 | } |
|---|
| | 130 | }); |
|---|
| | 131 | |
|---|
| | 132 | Map<SortedSet<Integer>, List<List<ObsAndTag>>> groupsByConstruct = new HashMap<SortedSet<Integer>, List<List<ObsAndTag>>>(); |
|---|
| | 133 | for (List<ObsAndTag> group : groups.values()) { |
|---|
| | 134 | SortedSet<Integer> questions = new TreeSet<Integer>(); |
|---|
| | 135 | for (ObsAndTag o : group) |
|---|
| | 136 | questions.add(o.getObs().getConcept().getConceptId()); |
|---|
| | 137 | List<List<ObsAndTag>> forThisConstruct = groupsByConstruct.get(questions); |
|---|
| | 138 | if (forThisConstruct == null) { |
|---|
| | 139 | forThisConstruct = new ArrayList<List<ObsAndTag>>(); |
|---|
| | 140 | groupsByConstruct.put(questions, forThisConstruct); |
|---|
| | 141 | } |
|---|
| | 142 | forThisConstruct.add(group); |
|---|
| | 143 | } |
|---|
| | 144 | |
|---|
| | 145 | work.add(data); |
|---|
| | 146 | obsGroups.add(groupsByConstruct); |
|---|
| | 147 | } |
|---|
| | 148 | |
|---|
| | 149 | Map<String, List<ObsAndTag>> first = work.get(0); |
|---|
| | 150 | Encounter firstEncounter = encounterList.get(0); |
|---|
| | 151 | int index = 0; |
|---|
| | 152 | for (Iterator<Map<String, List<ObsAndTag>>> i = work.listIterator(1); i.hasNext(); ) { |
|---|
| | 153 | ++index; |
|---|
| | 154 | Map<String, List<ObsAndTag>> current = i.next(); |
|---|
| | 155 | Set<String> allQuestions = new HashSet<String>(first.keySet()); |
|---|
| | 156 | allQuestions.addAll(current.keySet()); |
|---|
| | 157 | for (String question : allQuestions) { |
|---|
| | 158 | List<ObsAndTag> fromFirst = first.get(question); |
|---|
| | 159 | List<ObsAndTag> fromCurrent = current.get(question); |
|---|
| | 160 | ret.addAll( |
|---|
| | 161 | differencesHelper(patient, date, question, |
|---|
| | 162 | firstEncounter, encounterList.get(index), |
|---|
| | 163 | fromFirst, fromCurrent)); |
|---|
| | 164 | } |
|---|
| | 165 | } |
|---|
| | 166 | |
|---|
| | 167 | Map<SortedSet<Integer>, List<List<ObsAndTag>>> firstGroups = obsGroups.get(0); |
|---|
| | 168 | index = 0; |
|---|
| | 169 | for (int i = 1; i < obsGroups.size(); ++i) { |
|---|
| | 170 | Map<SortedSet<Integer>, List<List<ObsAndTag>>> currentGroups = obsGroups.get(i); |
|---|
| | 171 | ret.addAll( |
|---|
| | 172 | groupDifferencesHelper(patient, date, |
|---|
| | 173 | firstEncounter, encounterList.get(i), |
|---|
| | 174 | firstGroups, currentGroups)); |
|---|
| | 175 | } |
|---|
| | 176 | } |
|---|
| | 177 | return ret; |
|---|
| | 178 | } |
|---|
| | 179 | |
|---|
| | 180 | private static List<ReconcileFormError> groupDifferencesHelper( |
|---|
| | 181 | Patient patient, |
|---|
| | 182 | Date date, |
|---|
| | 183 | Encounter leftEncounter, |
|---|
| | 184 | Encounter rightEncounter, |
|---|
| | 185 | Map<SortedSet<Integer>, List<List<ObsAndTag>>> left, |
|---|
| | 186 | Map<SortedSet<Integer>, List<List<ObsAndTag>>> right) { |
|---|
| | 187 | |
|---|
| | 188 | List<ReconcileFormError> ret = new ArrayList<ReconcileFormError>(); |
|---|
| | 189 | Set<SortedSet<Integer>> allConstructs = new HashSet<SortedSet<Integer>>(left.keySet()); |
|---|
| | 190 | allConstructs.addAll(right.keySet()); |
|---|
| | 191 | |
|---|
| | 192 | for (SortedSet<Integer> construct : allConstructs) { |
|---|
| | 193 | List<List<ObsAndTag>> leftGroups = left.get(construct); |
|---|
| | 194 | List<List<ObsAndTag>> rightGroups = right.get(construct); |
|---|
| | 195 | |
|---|
| | 196 | if (leftGroups == null) { |
|---|
| | 197 | for (List<ObsAndTag> rightGroup : rightGroups) { |
|---|
| | 198 | ReconcileFormError e = new ReconcileFormError(); |
|---|
| | 199 | e.setPatient(patient); |
|---|
| | 200 | e.setEncounterDatetime(date); |
|---|
| | 201 | e.setEncounter1(leftEncounter); |
|---|
| | 202 | e.setEncounter2(rightEncounter); |
|---|
| | 203 | StringBuilder label = new StringBuilder("<u>GROUP</u>"); |
|---|
| | 204 | List<Obs> obs = new ArrayList<Obs>(); |
|---|
| | 205 | for (ObsAndTag o : rightGroup) { |
|---|
| | 206 | label.append("<br/>"); |
|---|
| | 207 | label.append(o.getObs().getConcept().getName() + " = " + o.getTag()); |
|---|
| | 208 | obs.add(o.getObs()); |
|---|
| | 209 | } |
|---|
| | 210 | e.setObsGroup2(obs); |
|---|
| | 211 | e.setLabel2(label.toString()); |
|---|
| | 212 | ret.add(e); |
|---|
| | 213 | } |
|---|
| | 214 | } else if (rightGroups == null) { |
|---|
| | 215 | for (List<ObsAndTag> leftGroup : leftGroups) { |
|---|
| | 216 | ReconcileFormError e = new ReconcileFormError(); |
|---|
| | 217 | e.setPatient(patient); |
|---|
| | 218 | e.setEncounterDatetime(date); |
|---|
| | 219 | e.setEncounter1(leftEncounter); |
|---|
| | 220 | e.setEncounter2(rightEncounter); |
|---|
| | 221 | StringBuilder label = new StringBuilder("<u>GROUP</u>"); |
|---|
| | 222 | List<Obs> obs = new ArrayList<Obs>(); |
|---|
| | 223 | for (ObsAndTag o : leftGroup) { |
|---|
| | 224 | label.append("<br/>"); |
|---|
| | 225 | label.append(o.getObs().getConcept().getName() + " = " + o.getTag()); |
|---|
| | 226 | obs.add(o.getObs()); |
|---|
| | 227 | } |
|---|
| | 228 | e.setObsGroup1(obs); |
|---|
| | 229 | e.setLabel1(label.toString()); |
|---|
| | 230 | ret.add(e); |
|---|
| | 231 | } |
|---|
| | 232 | } else { |
|---|
| | 233 | // go through everything in left, searching for matches in right, and marking those as we go |
|---|
| | 234 | // anything in left with no match in right is an error |
|---|
| | 235 | // anything in right that doesn't get marked is an error |
|---|
| | 236 | boolean[] usedInRight = new boolean[rightGroups.size()]; |
|---|
| | 237 | for (int i = leftGroups.size() - 1; i >= 0; --i) { |
|---|
| | 238 | List<ObsAndTag> leftGroup = leftGroups.get(i); |
|---|
| | 239 | boolean found = false; |
|---|
| | 240 | for (int j = rightGroups.size() - 1; j >= 0; --j) { |
|---|
| | 241 | if (usedInRight[j]) |
|---|
| | 242 | continue; |
|---|
| | 243 | List<ObsAndTag> rightGroup = rightGroups.get(j); |
|---|
| | 244 | if (equalGroups(leftGroup, rightGroup)) { |
|---|
| | 245 | usedInRight[j] = true; |
|---|
| | 246 | found = true; |
|---|
| | 247 | break; |
|---|
| | 248 | } |
|---|
| | 249 | } |
|---|
| | 250 | if (!found) { |
|---|
| | 251 | ReconcileFormError e = new ReconcileFormError(); |
|---|
| | 252 | e.setPatient(patient); |
|---|
| | 253 | e.setEncounterDatetime(date); |
|---|
| | 254 | e.setEncounter1(leftEncounter); |
|---|
| | 255 | e.setEncounter2(rightEncounter); |
|---|
| | 256 | StringBuilder label = new StringBuilder("<u>GROUP</u>"); |
|---|
| | 257 | List<Obs> obs = new ArrayList<Obs>(); |
|---|
| | 258 | for (ObsAndTag o : leftGroup) { |
|---|
| | 259 | label.append("<br/>"); |
|---|
| | 260 | label.append(o.getObs().getConcept().getName() + " = " + o.getTag()); |
|---|
| | 261 | obs.add(o.getObs()); |
|---|
| | 262 | } |
|---|
| | 263 | e.setObsGroup1(obs); |
|---|
| | 264 | e.setLabel1(label.toString()); |
|---|
| | 265 | ret.add(e); |
|---|
| | 266 | } |
|---|
| | 267 | } |
|---|
| | 268 | for (int i = usedInRight.length - 1; i >= 0; --i) { |
|---|
| | 269 | if (!usedInRight[i]) { |
|---|
| | 270 | ReconcileFormError e = new ReconcileFormError(); |
|---|
| | 271 | e.setPatient(patient); |
|---|
| | 272 | e.setEncounterDatetime(date); |
|---|
| | 273 | e.setEncounter1(leftEncounter); |
|---|
| | 274 | e.setEncounter2(rightEncounter); |
|---|
| | 275 | |
|---|
| | 276 | StringBuilder label = new StringBuilder("<u>GROUP</u>"); |
|---|
| | 277 | List<Obs> obs = new ArrayList<Obs>(); |
|---|
| | 278 | for (ObsAndTag o : rightGroups.get(i)) { |
|---|
| | 279 | label.append("<br/>"); |
|---|
| | 280 | label.append(o.getObs().getConcept().getName() + " = " + o.getTag()); |
|---|
| | 281 | obs.add(o.getObs()); |
|---|
| | 282 | } |
|---|
| | 283 | e.setObsGroup2(obs); |
|---|
| | 284 | e.setLabel2(label.toString()); |
|---|
| | 285 | ret.add(e); |
|---|
| | 286 | } |
|---|
| | 287 | } |
|---|
| | 288 | } |
|---|
| | 289 | } |
|---|
| | 290 | return ret; |
|---|
| | 291 | } |
|---|
| | 292 | |
|---|
| | 293 | |
|---|
| | 294 | |
|---|
| | 295 | private static boolean equalGroups(List<ObsAndTag> leftGroup, List<ObsAndTag> rightGroup) { |
|---|
| | 296 | if (leftGroup.size() != rightGroup.size()) |
|---|
| | 297 | return false; |
|---|
| | 298 | for (int i = leftGroup.size() - 1; i >= 0; --i) |
|---|
| | 299 | if (!leftGroup.get(i).equals(rightGroup.get(i))) |
|---|
| | 300 | return false; |
|---|
| | 301 | return true; |
|---|
| | 302 | } |
|---|
| | 303 | |
|---|
| | 304 | |
|---|
| | 305 | |
|---|
| | 306 | private static Collection<ReconcileFormError> differencesHelper( |
|---|
| | 307 | Patient patient, |
|---|
| | 308 | Date date, |
|---|
| | 309 | String question, |
|---|
| | 310 | Encounter leftEncounter, |
|---|
| | 311 | Encounter rightEncounter, |
|---|
| | 312 | List<ObsAndTag> left, |
|---|
| | 313 | List<ObsAndTag> right) { |
|---|
| | 314 | List<ReconcileFormError> ret = new ArrayList<ReconcileFormError>(); |
|---|
| | 315 | if (left == null) { |
|---|
| | 316 | for (ObsAndTag o : right) { |
|---|
| | 317 | ReconcileFormError e = new ReconcileFormError(); |
|---|
| | 318 | e.setPatient(patient); |
|---|
| | 319 | e.setEncounterDatetime(date); |
|---|
| | 320 | e.setQuestion(question); |
|---|
| | 321 | e.setEncounter1(leftEncounter); |
|---|
| | 322 | e.setEncounter2(rightEncounter); |
|---|
| | 323 | e.setObs1(null); |
|---|
| | 324 | e.setLabel1(null); |
|---|
| | 325 | e.setObs2(o.getObs()); |
|---|
| | 326 | e.setLabel2(o.getTag()); |
|---|
| | 327 | ret.add(e); |
|---|
| | 328 | } |
|---|
| | 329 | } else if (right == null) { |
|---|
| | 330 | for (ObsAndTag o : left) { |
|---|
| | 331 | ReconcileFormError e = new ReconcileFormError(); |
|---|
| | 332 | e.setPatient(patient); |
|---|
| | 333 | e.setEncounterDatetime(date); |
|---|
| | 334 | e.setQuestion(question); |
|---|
| | 335 | e.setEncounter1(leftEncounter); |
|---|
| | 336 | e.setEncounter2(rightEncounter); |
|---|
| | 337 | e.setObs1(o.getObs()); |
|---|
| | 338 | e.setLabel1(o.getTag()); |
|---|
| | 339 | e.setObs2(null); |
|---|
| | 340 | e.setLabel2(null); |
|---|
| | 341 | ret.add(e); |
|---|
| | 342 | } |
|---|
| | 343 | } else if (left.size() == 1 && right.size() == 1) { |
|---|
| | 344 | if (!left.get(0).equals(right.get(0))) { |
|---|
| | 345 | ReconcileFormError e = new ReconcileFormError(); |
|---|
| | 346 | e.setPatient(patient); |
|---|
| | 347 | e.setEncounterDatetime(date); |
|---|
| | 348 | e.setQuestion(question); |
|---|
| | 349 | e.setEncounter1(leftEncounter); |
|---|
| | 350 | e.setEncounter2(rightEncounter); |
|---|
| | 351 | e.setObs1(left.get(0).getObs()); |
|---|
| | 352 | e.setLabel1(left.get(0).getTag()); |
|---|
| | 353 | e.setObs2(right.get(0).getObs()); |
|---|
| | 354 | e.setLabel2(right.get(0).getTag()); |
|---|
| | 355 | ret.add(e); |
|---|
| | 356 | } |
|---|
| | 357 | } else { |
|---|
| | 358 | // go through everything in left, searching for matches in right, and marking those as we go |
|---|
| | 359 | // anything in left with no match in right is an error |
|---|
| | 360 | // anything in right that doesn't get markes is an error |
|---|
| | 361 | boolean[] usedInRight = new boolean[right.size()]; |
|---|
| | 362 | for (int i = left.size() - 1; i >= 0; --i) { |
|---|
| | 363 | ObsAndTag leftObs = left.get(i); |
|---|
| | 364 | boolean found = false; |
|---|
| | 365 | for (int j = right.size() - 1; j >= 0; --j) { |
|---|
| | 366 | if (usedInRight[j]) |
|---|
| | 367 | continue; |
|---|
| | 368 | ObsAndTag rightObs = right.get(j); |
|---|
| | 369 | if (leftObs.equals(rightObs)) { |
|---|
| | 370 | usedInRight[j] = true; |
|---|
| | 371 | found = true; |
|---|
| | 372 | break; |
|---|
| | 373 | } |
|---|
| | 374 | } |
|---|
| | 375 | if (!found) { |
|---|
| | 376 | ReconcileFormError e = new ReconcileFormError(); |
|---|
| | 377 | e.setPatient(patient); |
|---|
| | 378 | e.setEncounterDatetime(date); |
|---|
| | 379 | e.setQuestion(question); |
|---|
| | 380 | e.setEncounter1(leftEncounter); |
|---|
| | 381 | e.setEncounter2(rightEncounter); |
|---|
| | 382 | e.setObs1(leftObs.getObs()); |
|---|
| | 383 | e.setLabel1(leftObs.getTag()); |
|---|
| | 384 | e.setObs2(null); |
|---|
| | 385 | e.setLabel2(null); |
|---|
| | 386 | ret.add(e); |
|---|
| | 387 | } |
|---|
| | 388 | } |
|---|
| | 389 | for (int i = usedInRight.length - 1; i >= 0; --i) { |
|---|
| | 390 | if (!usedInRight[i]) { |
|---|
| | 391 | ReconcileFormError e = new ReconcileFormError(); |
|---|
| | 392 | e.setPatient(patient); |
|---|
| | 393 | e.setEncounterDatetime(date); |
|---|
| | 394 | e.setQuestion(question); |
|---|
| | 395 | e.setEncounter1(leftEncounter); |
|---|
| | 396 | e.setEncounter2(rightEncounter); |
|---|
| | 397 | e.setObs1(null); |
|---|
| | 398 | e.setObs2(right.get(i).getObs()); |
|---|
| | 399 | ret.add(e); |
|---|
| | 400 | } |
|---|
| | 401 | } |
|---|
| | 402 | } |
|---|
| | 403 | return ret; |
|---|
| | 404 | } |
|---|
| | 405 | |
|---|
| | 406 | |
|---|
| | 407 | /* |
|---|