extensions/net.sf.basedb.reggie/trunk/src/net/sf/basedb/reggie/servlet/IncaServlet.java

Code
Comments
Other
Rev Date Author Line
3786 17 Mar 16 olle 1 package net.sf.basedb.reggie.servlet;
3786 17 Mar 16 olle 2
3786 17 Mar 16 olle 3 import java.io.BufferedReader;
3786 17 Mar 16 olle 4 import java.io.IOException;
3786 17 Mar 16 olle 5 import java.io.InputStream;
3786 17 Mar 16 olle 6 import java.io.InputStreamReader;
4026 25 Jul 16 olle 7 import java.io.OutputStream;
4026 25 Jul 16 olle 8 import java.io.OutputStreamWriter;
5275 31 Jan 19 nicklas 9 import java.io.Writer;
4026 25 Jul 16 olle 10 import java.nio.charset.Charset;
3786 17 Mar 16 olle 11 import java.util.ArrayList;
3786 17 Mar 16 olle 12 import java.util.Arrays;
3786 17 Mar 16 olle 13 import java.util.Date;
3816 29 Mar 16 olle 14 import java.util.EnumSet;
3786 17 Mar 16 olle 15 import java.util.HashMap;
4720 28 Mar 18 nicklas 16 import java.util.HashSet;
3786 17 Mar 16 olle 17 import java.util.List;
5262 24 Jan 19 nicklas 18 import java.util.Map;
3816 29 Mar 16 olle 19 import java.util.Set;
4708 19 Mar 18 nicklas 20 import java.util.TreeSet;
3786 17 Mar 16 olle 21
5295 12 Feb 19 nicklas 22 import javax.crypto.Cipher;
5295 12 Feb 19 nicklas 23 import javax.crypto.CipherInputStream;
5295 12 Feb 19 nicklas 24 import javax.crypto.CipherOutputStream;
3786 17 Mar 16 olle 25 import javax.servlet.ServletException;
3786 17 Mar 16 olle 26 import javax.servlet.http.HttpServlet;
3786 17 Mar 16 olle 27 import javax.servlet.http.HttpServletRequest;
3786 17 Mar 16 olle 28 import javax.servlet.http.HttpServletResponse;
3786 17 Mar 16 olle 29
3786 17 Mar 16 olle 30 import org.json.simple.JSONArray;
3786 17 Mar 16 olle 31 import org.json.simple.JSONObject;
3786 17 Mar 16 olle 32
3786 17 Mar 16 olle 33 import net.sf.basedb.clients.web.fileupload.FileUpload;
3786 17 Mar 16 olle 34 import net.sf.basedb.clients.web.fileupload.UploadedFile;
3858 21 Apr 16 olle 35 import net.sf.basedb.core.AnnotationBatcher;
3858 21 Apr 16 olle 36 import net.sf.basedb.core.AnnotationBatcher.Change;
3786 17 Mar 16 olle 37 import net.sf.basedb.core.AnnotationType;
3786 17 Mar 16 olle 38 import net.sf.basedb.core.Application;
3786 17 Mar 16 olle 39 import net.sf.basedb.core.BioSource;
3786 17 Mar 16 olle 40 import net.sf.basedb.core.DbControl;
3917 02 May 16 olle 41 import net.sf.basedb.core.Include;
3858 21 Apr 16 olle 42 import net.sf.basedb.core.Item;
3786 17 Mar 16 olle 43 import net.sf.basedb.core.ItemQuery;
5273 31 Jan 19 nicklas 44 import net.sf.basedb.core.ItemResultIterator;
5273 31 Jan 19 nicklas 45 import net.sf.basedb.core.ProgressReporter;
3917 02 May 16 olle 46 import net.sf.basedb.core.Sample;
3786 17 Mar 16 olle 47 import net.sf.basedb.core.SessionControl;
3920 03 May 16 olle 48 import net.sf.basedb.core.SimpleProgressReporter;
3786 17 Mar 16 olle 49 import net.sf.basedb.core.query.Expressions;
3786 17 Mar 16 olle 50 import net.sf.basedb.core.query.Hql;
5260 22 Jan 19 nicklas 51 import net.sf.basedb.core.query.Orders;
3786 17 Mar 16 olle 52 import net.sf.basedb.core.query.Restrictions;
5256 21 Jan 19 nicklas 53 import net.sf.basedb.reggie.converter.IdentityConverter;
3786 17 Mar 16 olle 54 import net.sf.basedb.reggie.JsonUtil;
3786 17 Mar 16 olle 55 import net.sf.basedb.reggie.Reggie;
5387 26 Apr 19 nicklas 56 import net.sf.basedb.reggie.activity.ActivityDef;
3786 17 Mar 16 olle 57 import net.sf.basedb.reggie.counter.CounterService;
5332 20 Mar 19 nicklas 58 import net.sf.basedb.reggie.crypto.CryptoUtil;
3786 17 Mar 16 olle 59 import net.sf.basedb.reggie.dao.Annotationtype;
4459 21 Apr 17 nicklas 60 import net.sf.basedb.reggie.dao.ReferenceDateSource;
3786 17 Mar 16 olle 61 import net.sf.basedb.reggie.dao.ReggieRole;
3786 17 Mar 16 olle 62 import net.sf.basedb.reggie.dao.Subtype;
5273 31 Jan 19 nicklas 63 import net.sf.basedb.util.ChainedProgressReporter;
5252 21 Jan 19 nicklas 64 import net.sf.basedb.util.FileUtil;
5295 12 Feb 19 nicklas 65 import net.sf.basedb.util.MD5;
3786 17 Mar 16 olle 66 import net.sf.basedb.util.Values;
3889 28 Apr 16 olle 67 import net.sf.basedb.util.encode.TabCrLfEncoderDecoder;
3786 17 Mar 16 olle 68 import net.sf.basedb.util.error.ThrowableUtil;
5275 31 Jan 19 nicklas 69 import net.sf.basedb.util.export.TableWriter;
5288 08 Feb 19 nicklas 70 import net.sf.basedb.util.filter.Filter;
5290 11 Feb 19 nicklas 71 import net.sf.basedb.util.filter.StaticFilter;
3786 17 Mar 16 olle 72
3786 17 Mar 16 olle 73 public class IncaServlet 
3786 17 Mar 16 olle 74   extends HttpServlet 
3786 17 Mar 16 olle 75 {
3786 17 Mar 16 olle 76   private static final long serialVersionUID = 2550233926528990677L;
5261 23 Jan 19 nicklas 77
3972 25 May 16 olle 78   private static final String REPORT_TYPE_IMPORT = "import";
4026 25 Jul 16 olle 79   private static final String REPORT_TYPE_IMPORT_OUTPUT_CSV = "import_output_csv";
3972 25 May 16 olle 80   private static final String REPORT_TYPE_STATISTICS = "statistics";
3980 27 May 16 olle 81   private static final String REPORT_TYPE_STATISTICS_CSV = "statistics_csv";
3972 25 May 16 olle 82   private static final String INCA_IMPORT_REPORT_FILENAME = "inca_import_report.txt";
4026 25 Jul 16 olle 83   private static final String INCA_IMPORT_OUTPUT_CSV_FILENAME = "inca_import_output.csv";
3972 25 May 16 olle 84   private static final String INCA_STATISTICS_REPORT_FILENAME = "inca_statistics_report.txt";
3980 27 May 16 olle 85   private static final String INCA_STATISTICS_CSV_FILENAME = "inca_statistics.csv";
3972 25 May 16 olle 86   private static final String IMPORT_PROGRESS_ID = "inca-import-progress";
3972 25 May 16 olle 87   private static final String STATISTICS_PROGRESS_ID = "inca-stat-progress";
3786 17 Mar 16 olle 88
3786 17 Mar 16 olle 89   public IncaServlet()
3786 17 Mar 16 olle 90   {}
3786 17 Mar 16 olle 91
3786 17 Mar 16 olle 92   @Override
3786 17 Mar 16 olle 93   protected void doGet(HttpServletRequest req, HttpServletResponse resp)
3786 17 Mar 16 olle 94     throws ServletException, IOException 
3786 17 Mar 16 olle 95   {
3786 17 Mar 16 olle 96     String cmd = req.getParameter("cmd");
3786 17 Mar 16 olle 97     JsonUtil.setJsonResponseHeaders(resp);
3786 17 Mar 16 olle 98     
3786 17 Mar 16 olle 99     JSONObject json = new JSONObject();
3786 17 Mar 16 olle 100     json.put("status", "ok");
3786 17 Mar 16 olle 101     
3975 26 May 16 nicklas 102     final SessionControl sc = Reggie.getSessionControl(req);
3786 17 Mar 16 olle 103     DbControl dc = null;
3786 17 Mar 16 olle 104     try
3786 17 Mar 16 olle 105     {
3951 18 May 16 olle 106       if ("CheckForIncaReportFile".equals(cmd))
3786 17 Mar 16 olle 107       {
3951 18 May 16 olle 108         String reportType = req.getParameter("reporttype");
5294 12 Feb 19 nicklas 109         boolean fileFound = checkForReportFile(sc, reportType);
4032 28 Jul 16 olle 110         json.put("reporttype", reportType);
4032 28 Jul 16 olle 111         json.put("incaReportFileExists", fileFound);
4032 28 Jul 16 olle 112       }
4032 28 Jul 16 olle 113       else if ("CheckForIncaReportFiles".equals(cmd))
4032 28 Jul 16 olle 114       {
4032 28 Jul 16 olle 115         String reportTypes = req.getParameter("reporttypes");
3786 17 Mar 16 olle 116
4032 28 Jul 16 olle 117         JSONObject jsonResult = new JSONObject();
4032 28 Jul 16 olle 118         if (reportTypes != null)
3856 18 Apr 16 olle 119         {
4032 28 Jul 16 olle 120           String[] reportTypeArr = reportTypes.split(",", -1);
4032 28 Jul 16 olle 121           for (int i = 0; i < reportTypeArr.length; i++)
4032 28 Jul 16 olle 122           {
5415 09 May 19 nicklas 123             String reportType = reportTypeArr[i];
5294 12 Feb 19 nicklas 124             boolean fileFound = checkForReportFile(sc, reportType);
4032 28 Jul 16 olle 125             jsonResult.put(reportType, fileFound);
4032 28 Jul 16 olle 126           }
3856 18 Apr 16 olle 127         }
4032 28 Jul 16 olle 128         json.put("result", jsonResult);
3856 18 Apr 16 olle 129       }
3951 18 May 16 olle 130       else if ("DownloadIncaReportFile".equals(cmd))
3856 18 Apr 16 olle 131       {
5252 21 Jan 19 nicklas 132         json = null; // No JSON output
5252 21 Jan 19 nicklas 133         
6336 16 Jun 21 nicklas 134         dc = sc.newDbControl(":INCA Import");
5252 21 Jan 19 nicklas 135         ReggieRole.checkPermission(dc, "'" + cmd + "' wizard", ReggieRole.PATIENT_CURATOR, ReggieRole.ADMINISTRATOR);
5252 21 Jan 19 nicklas 136
3951 18 May 16 olle 137         String reportType = req.getParameter("reporttype");
3951 18 May 16 olle 138         String filename = fetchReportFileName(reportType);
5279 05 Feb 19 nicklas 139         String reportPath = getReportCacheKey(sc, reportType);
3856 18 Apr 16 olle 140
3786 17 Mar 16 olle 141         resp.setHeader("Content-Disposition", "attachment; filename=" + filename);
3786 17 Mar 16 olle 142         resp.setContentType("text/plain");
3786 17 Mar 16 olle 143         resp.setCharacterEncoding("UTF-8");
5279 05 Feb 19 nicklas 144         
5295 12 Feb 19 nicklas 145         InputStream in = Application.getStaticCache().read(reportPath, 5);
5295 12 Feb 19 nicklas 146
5332 20 Mar 19 nicklas 147         Cipher c = CryptoUtil.createSessionCipher(sc, Cipher.DECRYPT_MODE, reportType);
5295 12 Feb 19 nicklas 148         in = new CipherInputStream(in, c);
5295 12 Feb 19 nicklas 149         
5252 21 Jan 19 nicklas 150         OutputStream out = resp.getOutputStream();
5295 12 Feb 19 nicklas 151         FileUtil.copy(in, out);
5295 12 Feb 19 nicklas 152         in.close();
5252 21 Jan 19 nicklas 153         out.flush();
5252 21 Jan 19 nicklas 154         out.close();
3786 17 Mar 16 olle 155       }
3786 17 Mar 16 olle 156     }
3786 17 Mar 16 olle 157     catch (Throwable t)
3786 17 Mar 16 olle 158     {
3786 17 Mar 16 olle 159       t.printStackTrace();
5252 21 Jan 19 nicklas 160       if (json != null)
5252 21 Jan 19 nicklas 161       {
5252 21 Jan 19 nicklas 162         json.clear();
5252 21 Jan 19 nicklas 163         json.put("status", "error");
5252 21 Jan 19 nicklas 164         json.put("message", t.getMessage());
5252 21 Jan 19 nicklas 165         json.put("stacktrace", ThrowableUtil.stackTraceToString(t));
5252 21 Jan 19 nicklas 166       }
5252 21 Jan 19 nicklas 167       else
5252 21 Jan 19 nicklas 168       {
5252 21 Jan 19 nicklas 169         resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, t.getClass().getName() + ": " + t.getMessage());
5252 21 Jan 19 nicklas 170       }
3786 17 Mar 16 olle 171     }
3786 17 Mar 16 olle 172     finally
3786 17 Mar 16 olle 173     {
3786 17 Mar 16 olle 174       if (dc != null) dc.close();
5252 21 Jan 19 nicklas 175       if (json != null)
5252 21 Jan 19 nicklas 176       {
5252 21 Jan 19 nicklas 177         json.writeJSONString(resp.getWriter());
5252 21 Jan 19 nicklas 178       }
3786 17 Mar 16 olle 179     }
3786 17 Mar 16 olle 180   }
3786 17 Mar 16 olle 181
3786 17 Mar 16 olle 182   @Override
3786 17 Mar 16 olle 183   protected void doPost(HttpServletRequest req, HttpServletResponse resp)
3786 17 Mar 16 olle 184     throws ServletException, IOException 
3786 17 Mar 16 olle 185   {
3786 17 Mar 16 olle 186     String cmd = req.getParameter("cmd");
3786 17 Mar 16 olle 187     JsonUtil.setJsonResponseHeaders(resp);
3786 17 Mar 16 olle 188     
3786 17 Mar 16 olle 189     JSONObject json = new JSONObject();
3786 17 Mar 16 olle 190     json.put("status", "ok");
3786 17 Mar 16 olle 191     
3786 17 Mar 16 olle 192     JSONArray jsonMessages = new JSONArray();
3786 17 Mar 16 olle 193
3975 26 May 16 nicklas 194     final SessionControl sc = Reggie.getSessionControl(req);
3786 17 Mar 16 olle 195     DbControl dc = null;
5288 08 Feb 19 nicklas 196     String progressId = null;
3786 17 Mar 16 olle 197     try
3786 17 Mar 16 olle 198     {
3786 17 Mar 16 olle 199       if ("ImportInca".equals(cmd))
3786 17 Mar 16 olle 200       {
6336 16 Jun 21 nicklas 201         dc = sc.newDbControl(":INCA import");
5280 06 Feb 19 nicklas 202         
3928 12 May 16 olle 203         ReggieRole.checkPermission(dc, "'" + cmd + "' wizard", ReggieRole.PATIENT_CURATOR, ReggieRole.ADMINISTRATOR);
3928 12 May 16 olle 204
3920 03 May 16 olle 205         SimpleProgressReporter progress = new SimpleProgressReporter(null);
3950 18 May 16 olle 206         progressId = IMPORT_PROGRESS_ID;
3950 18 May 16 olle 207         sc.setSessionSetting(progressId, progress);
5256 21 Jan 19 nicklas 208
5270 29 Jan 19 nicklas 209         // 'simplecheck' or 'fullcheck', a null value means actual import
5270 29 Jan 19 nicklas 210         String checkType = Values.getStringOrNull(req.getParameter("checkType"));
5415 09 May 19 nicklas 211         Date incaExportDate = Reggie.CONVERTER_STRING_TO_DATE.convert(req.getParameter("exportdate"));
5269 28 Jan 19 nicklas 212         Date incaImportDate = new Date();
5269 28 Jan 19 nicklas 213         
5256 21 Jan 19 nicklas 214         // Parse the INCA file
5256 21 Jan 19 nicklas 215         FileUpload fileUpload = new FileUpload(req);
5256 21 Jan 19 nicklas 216         UploadedFile uf = fileUpload.next();
5269 28 Jan 19 nicklas 217         
5273 31 Jan 19 nicklas 218         progress.display(5, "Parsing '" + uf.getFilename() + "'...");
5273 31 Jan 19 nicklas 219         
5288 08 Feb 19 nicklas 220         IncaFile incaFile = new IncaFile(uf.getFilename(), false);
5270 29 Jan 19 nicklas 221         incaFile.exportDate = incaExportDate;
5269 28 Jan 19 nicklas 222         
5269 28 Jan 19 nicklas 223         incaFile.parse(uf.getInputStream());
5260 22 Jan 19 nicklas 224
5269 28 Jan 19 nicklas 225         // Find columns that map to defined INCA2_* annotation types
5280 06 Feb 19 nicklas 226         /*
5280 06 Feb 19 nicklas 227           Include options to make a query only return items shared
5280 06 Feb 19 nicklas 228           to either the current project or the logged-in user.
5280 06 Feb 19 nicklas 229           The latter is needed for annotation types that represent sensitive PAD numbers,
5280 06 Feb 19 nicklas 230           and therefore only are accessible to users belonging to
5280 06 Feb 19 nicklas 231           the PatientCurator group.
5280 06 Feb 19 nicklas 232         */
5280 06 Feb 19 nicklas 233         ItemQuery<AnnotationType> query = AnnotationType.getQuery(null);
5280 06 Feb 19 nicklas 234         query.setIncludes(EnumSet.of(Include.IN_PROJECT, Include.SHARED));
5280 06 Feb 19 nicklas 235         query.restrict(Restrictions.like(Hql.property("name"), Expressions.string("INCA2_" + "%")));
5280 06 Feb 19 nicklas 236         query.order(Orders.asc(Hql.property("name")));
5280 06 Feb 19 nicklas 237         List<AnnotationType> incaAnnotationTypes = query.list(dc);
5289 11 Feb 19 nicklas 238         incaFile.mapAnnotationColumns(incaAnnotationTypes, false);
5256 21 Jan 19 nicklas 239         
5269 28 Jan 19 nicklas 240         // Check for duplicate laterality
5269 28 Jan 19 nicklas 241         incaFile.doLateralityCheck();
5269 28 Jan 19 nicklas 242
5269 28 Jan 19 nicklas 243         // Check data values against annotation types
5288 08 Feb 19 nicklas 244         incaFile.doDataCheck(null);
5269 28 Jan 19 nicklas 245
5270 29 Jan 19 nicklas 246         if (checkType == null || "fullcheck".equals(checkType))
5267 25 Jan 19 nicklas 247         {
5273 31 Jan 19 nicklas 248           // Remaining data lines are checked against the database for a patient and case
5273 31 Jan 19 nicklas 249           ChainedProgressReporter cProgress = new ChainedProgressReporter(progress);
5279 05 Feb 19 nicklas 250           int progressEnd = checkType == null ? 30 : 90;
5279 05 Feb 19 nicklas 251           cProgress.setRange(5, progressEnd);
5273 31 Jan 19 nicklas 252           incaFile.doDatabaseMapping(dc, cProgress);
5279 05 Feb 19 nicklas 253           
5279 05 Feb 19 nicklas 254           cProgress.setRange(progressEnd, progressEnd+5);
5293 12 Feb 19 nicklas 255           createIncaOutputFile(sc, incaFile, REPORT_TYPE_IMPORT_OUTPUT_CSV, cProgress);
5267 25 Jan 19 nicklas 256         }
5267 25 Jan 19 nicklas 257         
5273 31 Jan 19 nicklas 258         // Summarize all excluded data lines
5273 31 Jan 19 nicklas 259         incaFile.summarizeExcludedLines();
5273 31 Jan 19 nicklas 260         dc.close();
5273 31 Jan 19 nicklas 261         
5270 29 Jan 19 nicklas 262         if (checkType == null)
3786 17 Mar 16 olle 263         {
5273 31 Jan 19 nicklas 264           // Actual import
3858 21 Apr 16 olle 265           // Get new DbControl for use with AnnotationBatcher
6336 16 Jun 21 nicklas 266           dc = sc.newDbControl(dc.getName());
5269 28 Jan 19 nicklas 267           
5273 31 Jan 19 nicklas 268           ChainedProgressReporter cProgress = new ChainedProgressReporter(progress);
5279 05 Feb 19 nicklas 269           cProgress.setRange(35, 95);
5273 31 Jan 19 nicklas 270           incaFile.doImport(dc, cProgress);
5269 28 Jan 19 nicklas 271           
5273 31 Jan 19 nicklas 272           progress.display(98, "Comitting transaction. This may take a while...");
5421 13 May 19 nicklas 273           ActivityDef.UPDATED_INCA.create(dc, incaFile.numCasesUpdated);
5279 05 Feb 19 nicklas 274           dc.commit();
5273 31 Jan 19 nicklas 275           
5273 31 Jan 19 nicklas 276           jsonMessages.add("Processed "  + incaFile.numCasesChecked + " data lines for " + incaFile.numCasesChecked + " cases.");
5273 31 Jan 19 nicklas 277           jsonMessages.add(incaFile.numCasesUpdated + " cases updated with " + 
5273 31 Jan 19 nicklas 278             incaFile.numAnnotations[Change.ADDED.ordinal()] + " new, " + 
5273 31 Jan 19 nicklas 279             incaFile.numAnnotations[Change.UPDATED.ordinal()] + " modified and " +
5273 31 Jan 19 nicklas 280             incaFile.numAnnotations[Change.DELETED.ordinal()] + " removed INCA annotations.");
5273 31 Jan 19 nicklas 281           jsonMessages.add(incaFile.numAnnotations[Change.NO_CHANGE.ordinal()] + " annotations without change.");
5273 31 Jan 19 nicklas 282           if (incaFile.numCasesUpdated < incaFile.numCasesChecked)
3858 21 Apr 16 olle 283           {
5273 31 Jan 19 nicklas 284             jsonMessages.add((incaFile.numCasesChecked - incaFile.numCasesUpdated) + " cases without change.");
3858 21 Apr 16 olle 285           }
3786 17 Mar 16 olle 286         }
5287 08 Feb 19 nicklas 287         incaFile.endDate = new Date();
5263 24 Jan 19 nicklas 288         
3786 17 Mar 16 olle 289         // Create INCA import report file
5279 05 Feb 19 nicklas 290         createIncaImportReportFile(sc, incaFile, checkType);
5273 31 Jan 19 nicklas 291     
5273 31 Jan 19 nicklas 292         JSONObject jsonIncaFile = incaFile.getFileInfoInJSON(new JSONObject());
5273 31 Jan 19 nicklas 293
5263 24 Jan 19 nicklas 294         json.put("fileInfo", jsonIncaFile);
5270 29 Jan 19 nicklas 295         json.put("checkType", checkType);
5273 31 Jan 19 nicklas 296         progress.display(100, "Completed");
3786 17 Mar 16 olle 297       }
3963 20 May 16 olle 298       else if ("IncaStatistics".equals(cmd))
3963 20 May 16 olle 299       {
6336 16 Jun 21 nicklas 300         dc = sc.newDbControl(":INCA statistics");
3963 20 May 16 olle 301
3963 20 May 16 olle 302         ReggieRole.checkPermission(dc, "'" + cmd + "' wizard", ReggieRole.PATIENT_CURATOR, ReggieRole.ADMINISTRATOR);
3963 20 May 16 olle 303
3963 20 May 16 olle 304         SimpleProgressReporter progress = new SimpleProgressReporter(null);
3963 20 May 16 olle 305         progressId = STATISTICS_PROGRESS_ID;
3963 20 May 16 olle 306         sc.setSessionSetting(progressId, progress);
5287 08 Feb 19 nicklas 307
5287 08 Feb 19 nicklas 308         String checkType = Values.getStringOrNull(req.getParameter("checkType"));
5287 08 Feb 19 nicklas 309         
5288 08 Feb 19 nicklas 310         // Which date to use for statistics selection? 'diagnosis' or 'operation' date
5288 08 Feb 19 nicklas 311         KeyColumn dateVariableCol = "operation".equals(req.getParameter("statdatevar")) ? 
5288 08 Feb 19 nicklas 312           KeyColumn.OP_KIR_DAT : KeyColumn.A_DIAG_DAT;
5288 08 Feb 19 nicklas 313         
5288 08 Feb 19 nicklas 314         // Date interval for the statistics
5288 08 Feb 19 nicklas 315         Date statStartDate = Reggie.CONVERTER_STRING_TO_DATE.convert(req.getParameter("startdate"));
5288 08 Feb 19 nicklas 316         Date statEndDate = Reggie.CONVERTER_STRING_TO_DATE.convert(req.getParameter("enddate"));
5288 08 Feb 19 nicklas 317         
5288 08 Feb 19 nicklas 318         // Filter for cancer type
5288 08 Feb 19 nicklas 319         String cancerType = Values.getStringOrNull(req.getParameter("cancertype"));
5288 08 Feb 19 nicklas 320         
5287 08 Feb 19 nicklas 321         // Parse the INCA file
5287 08 Feb 19 nicklas 322         FileUpload fileUpload = new FileUpload(req);
5287 08 Feb 19 nicklas 323         UploadedFile uf = fileUpload.next();
5287 08 Feb 19 nicklas 324
5287 08 Feb 19 nicklas 325         progress.display(5, "Parsing '" + uf.getFilename() + "'...");
5287 08 Feb 19 nicklas 326         
5288 08 Feb 19 nicklas 327         IncaFile incaFile = new IncaFile(uf.getFilename(), true);
5287 08 Feb 19 nicklas 328         incaFile.parse(uf.getInputStream());
5287 08 Feb 19 nicklas 329         
5288 08 Feb 19 nicklas 330         // Find columns that map to defined INCA2_* annotation types
5288 08 Feb 19 nicklas 331         ItemQuery<AnnotationType> query = AnnotationType.getQuery(null);
5288 08 Feb 19 nicklas 332         query.setIncludes(EnumSet.of(Include.IN_PROJECT, Include.SHARED));
5288 08 Feb 19 nicklas 333         query.restrict(Restrictions.like(Hql.property("name"), Expressions.string("INCA2_" + "%")));
5288 08 Feb 19 nicklas 334         query.order(Orders.asc(Hql.property("name")));
5288 08 Feb 19 nicklas 335         List<AnnotationType> incaAnnotationTypes = query.list(dc);
5289 11 Feb 19 nicklas 336         incaFile.mapAnnotationColumns(incaAnnotationTypes, true);
5288 08 Feb 19 nicklas 337
5288 08 Feb 19 nicklas 338         // Check that we have all columns required for statistics
5288 08 Feb 19 nicklas 339         // and that they are mapped to annotation types
5288 08 Feb 19 nicklas 340         KeyColumn[] columnsForStatistics = {KeyColumn.CANCER_TYPE, dateVariableCol, 
5288 08 Feb 19 nicklas 341             KeyColumn.ER_STATUS, KeyColumn.PGR_STATUS, KeyColumn.HER2_STATUS, 
5288 08 Feb 19 nicklas 342             KeyColumn.AGE, KeyColumn.NHG_STATUS, KeyColumn.TUMOR_SIZE };
5288 08 Feb 19 nicklas 343         Header[] headersForStatistics = incaFile.checkKeyColumns(true, columnsForStatistics);
5288 08 Feb 19 nicklas 344         
5288 08 Feb 19 nicklas 345         // Check for duplicate laterality
5288 08 Feb 19 nicklas 346         incaFile.doLateralityCheck();
5288 08 Feb 19 nicklas 347
5288 08 Feb 19 nicklas 348         // Check data values against key columns requried for statistics
5288 08 Feb 19 nicklas 349         incaFile.doDataCheck(headersForStatistics);
5288 08 Feb 19 nicklas 350
5288 08 Feb 19 nicklas 351         // Perform filtering on date
5288 08 Feb 19 nicklas 352         int dateColIndex = incaFile.keyColumns[dateVariableCol.ordinal()];
5290 11 Feb 19 nicklas 353         DateFilter dateFilter = new DateFilter(dateColIndex, statStartDate, statEndDate);
5288 08 Feb 19 nicklas 354         incaFile.doFilter(dateFilter, ExcludedLine.FILTERED_BY_DATE);
5288 08 Feb 19 nicklas 355         
5288 08 Feb 19 nicklas 356         // Perform filtering on cancer type
5290 11 Feb 19 nicklas 357         ColumnValueFilter cancerTypeFilter = null;
5288 08 Feb 19 nicklas 358         if (cancerType != null)
5288 08 Feb 19 nicklas 359         {
5288 08 Feb 19 nicklas 360           int cancerTypeColIndex = incaFile.keyColumns[KeyColumn.CANCER_TYPE.ordinal()];
5290 11 Feb 19 nicklas 361           cancerTypeFilter = new ColumnValueFilter(cancerTypeColIndex, cancerType.equals("invasive") ? 1 : 2);
5288 08 Feb 19 nicklas 362           incaFile.doFilter(cancerTypeFilter, ExcludedLine.FILTERED_BY_CANCER_TYPE);
5290 11 Feb 19 nicklas 363         }      
5292 12 Feb 19 nicklas 364
5292 12 Feb 19 nicklas 365         if (checkType == null || "fullstatistics".equals(checkType))
5292 12 Feb 19 nicklas 366         {
5292 12 Feb 19 nicklas 367           // Remaining data lines are checked against the database for a patient and case
5292 12 Feb 19 nicklas 368           ChainedProgressReporter cProgress = new ChainedProgressReporter(progress);
5292 12 Feb 19 nicklas 369           cProgress.setRange(5, 90);
5292 12 Feb 19 nicklas 370           incaFile.doDatabaseMapping(dc, cProgress);
5292 12 Feb 19 nicklas 371           
5293 12 Feb 19 nicklas 372           cProgress.setRange(90, 95);
5293 12 Feb 19 nicklas 373           createIncaOutputFile(sc, incaFile, REPORT_TYPE_STATISTICS_CSV, cProgress);
5292 12 Feb 19 nicklas 374         }
5292 12 Feb 19 nicklas 375
5287 08 Feb 19 nicklas 376         // Summarize all excluded data lines
5287 08 Feb 19 nicklas 377         incaFile.summarizeExcludedLines();
5290 11 Feb 19 nicklas 378
5292 12 Feb 19 nicklas 379         StatisticalVariable allEntries = new StatisticalVariable("All entries", -1, null);
5290 11 Feb 19 nicklas 380         allEntries.addGroup("All", new StaticFilter<>(true));
5290 11 Feb 19 nicklas 381         
5292 12 Feb 19 nicklas 382         StatisticalVariable erStatus = new StatisticalVariable("ER Status", incaFile.keyColumns[KeyColumn.ER_STATUS.ordinal()], null);
5290 11 Feb 19 nicklas 383         erStatus.addValueGroup("Positive", 1).addValueGroup("Negative", 2).addNaGroup();
5290 11 Feb 19 nicklas 384         
5292 12 Feb 19 nicklas 385         StatisticalVariable pgrStatus = new StatisticalVariable("PgR Status", incaFile.keyColumns[KeyColumn.PGR_STATUS.ordinal()], null);
5290 11 Feb 19 nicklas 386         pgrStatus.addValueGroup("Positive", 1).addValueGroup("Negative", 2).addNaGroup();
5290 11 Feb 19 nicklas 387
5292 12 Feb 19 nicklas 388         StatisticalVariable her2Status = new StatisticalVariable("HER2 Status", incaFile.keyColumns[KeyColumn.HER2_STATUS.ordinal()], null);
5290 11 Feb 19 nicklas 389         her2Status.addValueGroup("Positive", 1).addValueGroup("Negative", 2).addNaGroup();
5290 11 Feb 19 nicklas 390         
5292 12 Feb 19 nicklas 391         StatisticalVariable age = new StatisticalVariable("Age", incaFile.keyColumns[KeyColumn.AGE.ordinal()], "years");
5290 11 Feb 19 nicklas 392         age.addGroup("<50", new BetweenFilter(age.column, -1, 49))
5290 11 Feb 19 nicklas 393           .addGroup("50-70", new BetweenFilter(age.column, 50, 70))
5290 11 Feb 19 nicklas 394           .addGroup(">70", new BetweenFilter(age.column, 71, -1))
5290 11 Feb 19 nicklas 395           .addNaGroup();
5290 11 Feb 19 nicklas 396         
5292 12 Feb 19 nicklas 397         StatisticalVariable nhg = new StatisticalVariable("NHG", incaFile.keyColumns[KeyColumn.NHG_STATUS.ordinal()], null);
5290 11 Feb 19 nicklas 398         nhg.addValueGroup("Grade 1", 1).addValueGroup("Grade 2", 2).addValueGroup("Grade 3", 3).addNaGroup();
5290 11 Feb 19 nicklas 399
5292 12 Feb 19 nicklas 400         StatisticalVariable tumorSize = new StatisticalVariable("Tumor size", incaFile.keyColumns[KeyColumn.TUMOR_SIZE.ordinal()], "mm");
5290 11 Feb 19 nicklas 401         tumorSize.addGroup("<10", new BetweenFilter(tumorSize.column, -1, 9))
5290 11 Feb 19 nicklas 402           .addGroup("10-20", new BetweenFilter(tumorSize.column, 10, 20))
5290 11 Feb 19 nicklas 403           .addGroup(">20", new BetweenFilter(tumorSize.column, 21, -1))
5290 11 Feb 19 nicklas 404           .addNaGroup();
5290 11 Feb 19 nicklas 405         
5290 11 Feb 19 nicklas 406         incaFile.doStatistics(allEntries, erStatus, pgrStatus, her2Status, age, nhg, tumorSize);
5290 11 Feb 19 nicklas 407         
5287 08 Feb 19 nicklas 408         dc.close();
5287 08 Feb 19 nicklas 409
5287 08 Feb 19 nicklas 410         incaFile.endDate = new Date();
5287 08 Feb 19 nicklas 411         
5287 08 Feb 19 nicklas 412         // Create INCA statistics report file
5290 11 Feb 19 nicklas 413         createIncaStatisticsReportFile(sc, incaFile, checkType, dateFilter, cancerTypeFilter);
5289 11 Feb 19 nicklas 414         
5287 08 Feb 19 nicklas 415         JSONObject jsonIncaFile = incaFile.getFileInfoInJSON(new JSONObject());
5287 08 Feb 19 nicklas 416
5287 08 Feb 19 nicklas 417         json.put("fileInfo", jsonIncaFile);
5287 08 Feb 19 nicklas 418         json.put("checkType", checkType);
5287 08 Feb 19 nicklas 419         progress.display(100, "Completed");
5287 08 Feb 19 nicklas 420
3963 20 May 16 olle 421       }
4032 28 Jul 16 olle 422       else if ("DeleteIncaReportFile".equals(cmd))
4032 28 Jul 16 olle 423       {
6336 16 Jun 21 nicklas 424         dc = sc.newDbControl(":INCA import");
4032 28 Jul 16 olle 425
4032 28 Jul 16 olle 426         ReggieRole.checkPermission(dc, "'" + cmd + "' wizard", ReggieRole.PATIENT_CURATOR, ReggieRole.ADMINISTRATOR);
4032 28 Jul 16 olle 427         String reportType = req.getParameter("reporttype");
4032 28 Jul 16 olle 428         
4032 28 Jul 16 olle 429         // Currently restrict report file removal to INCA import output CSV file
4032 28 Jul 16 olle 430         if (reportType != null && reportType.equals(REPORT_TYPE_IMPORT_OUTPUT_CSV))
4032 28 Jul 16 olle 431         {
5294 12 Feb 19 nicklas 432           String reportFilePath = getReportCacheKey(sc, reportType);
5294 12 Feb 19 nicklas 433           boolean deleted = Application.getStaticCache().delete(reportFilePath, 5);
5294 12 Feb 19 nicklas 434           if (deleted)
4032 28 Jul 16 olle 435           {
5294 12 Feb 19 nicklas 436             jsonMessages.add("The file has been deleted");
4032 28 Jul 16 olle 437           }
5294 12 Feb 19 nicklas 438           else
4032 28 Jul 16 olle 439           {
5294 12 Feb 19 nicklas 440             jsonMessages.add("The file could not be deleted");
4032 28 Jul 16 olle 441           }
4032 28 Jul 16 olle 442         }
5294 12 Feb 19 nicklas 443         else
4032 28 Jul 16 olle 444         {
5294 12 Feb 19 nicklas 445           jsonMessages.add("Not allowed");
4032 28 Jul 16 olle 446         }
4032 28 Jul 16 olle 447       }
4032 28 Jul 16 olle 448       json.put("cmd", cmd);
3786 17 Mar 16 olle 449       json.put("messages", jsonMessages);
3786 17 Mar 16 olle 450       CounterService.getInstance().setForceCount();
3786 17 Mar 16 olle 451     }
3786 17 Mar 16 olle 452     catch (Throwable t)
3786 17 Mar 16 olle 453     {
3786 17 Mar 16 olle 454       t.printStackTrace();
3786 17 Mar 16 olle 455       json.clear();
3786 17 Mar 16 olle 456       json.put("status", "error");
3786 17 Mar 16 olle 457       json.put("message", t.getMessage());
3786 17 Mar 16 olle 458       json.put("stacktrace", ThrowableUtil.stackTraceToString(t));
3786 17 Mar 16 olle 459     }
3786 17 Mar 16 olle 460     finally
3786 17 Mar 16 olle 461     {
3786 17 Mar 16 olle 462       if (dc != null) dc.close();
5288 08 Feb 19 nicklas 463       if (sc != null && progressId != null) 
3786 17 Mar 16 olle 464       {
5288 08 Feb 19 nicklas 465         sc.setSessionSetting(progressId, null);
3786 17 Mar 16 olle 466       }
5288 08 Feb 19 nicklas 467       json.writeJSONString(resp.getWriter());
3786 17 Mar 16 olle 468     }    
3786 17 Mar 16 olle 469   }
3786 17 Mar 16 olle 470
5295 12 Feb 19 nicklas 471
5295 12 Feb 19 nicklas 472   
5295 12 Feb 19 nicklas 473   /**
5293 12 Feb 19 nicklas 474     Creates an INCA output file in CSV format with columns
5275 31 Jan 19 nicklas 475     corresponding to INCA annotation types, plus:
5275 31 Jan 19 nicklas 476     PersonalNo: a column with patient item name for entries that could be mapped to SCAN-B patients items
5275 31 Jan 19 nicklas 477     PAT_ID: a column with the temporary patient id in the import file
5293 12 Feb 19 nicklas 478     HasSpecimen: Yes or No if the line was mapped to a case, empty otherwise
5293 12 Feb 19 nicklas 479     Flag: Reasons for excluding a data line in the import or statistics
5275 31 Jan 19 nicklas 480   */
5293 12 Feb 19 nicklas 481   private void createIncaOutputFile(SessionControl sc, IncaFile incaFile, String reportType, ProgressReporter progress)
4026 25 Jul 16 olle 482   {
5279 05 Feb 19 nicklas 483     progress.display(0, "Writing output CSV file...");
5293 12 Feb 19 nicklas 484     String outputFilePath = getReportCacheKey(sc, reportType);
5275 31 Jan 19 nicklas 485     TableWriter writer = null;
5279 05 Feb 19 nicklas 486     OutputStream out = null;
5275 31 Jan 19 nicklas 487     
5275 31 Jan 19 nicklas 488     // A line is all mapped annotations + 3 extra columns
5293 12 Feb 19 nicklas 489     Object[] data = new Object[4+incaFile.mappedHeaders.size()];
4026 25 Jul 16 olle 490     try
4026 25 Jul 16 olle 491     {
5332 20 Mar 19 nicklas 492       Cipher c = CryptoUtil.createSessionCipher(sc, Cipher.ENCRYPT_MODE, reportType);
5295 12 Feb 19 nicklas 493       out = new CipherOutputStream(Application.getStaticCache().write(outputFilePath, 5), c);
5295 12 Feb 19 nicklas 494
5279 05 Feb 19 nicklas 495       writer = new TableWriter(new OutputStreamWriter(out, Charset.forName("UTF-8")));
5275 31 Jan 19 nicklas 496       writer.setDataSeparator("\t");
5275 31 Jan 19 nicklas 497       writer.setNullValue("");
5275 31 Jan 19 nicklas 498       writer.setEncoder(new TabCrLfEncoderDecoder(true));
4026 25 Jul 16 olle 499
5275 31 Jan 19 nicklas 500       data[0] = "PersonalNo";
5275 31 Jan 19 nicklas 501       data[1] = "PAT_ID";
5293 12 Feb 19 nicklas 502       data[2] = "HasSpecimen";
5293 12 Feb 19 nicklas 503       data[3] = "Flag";
5293 12 Feb 19 nicklas 504       int i = 4;
5275 31 Jan 19 nicklas 505       for (Header h : incaFile.headers)
4026 25 Jul 16 olle 506       {
5275 31 Jan 19 nicklas 507         if (h.annotationType == null) continue;
5275 31 Jan 19 nicklas 508         data[i] = h.name;
5275 31 Jan 19 nicklas 509         i++;
4026 25 Jul 16 olle 510       }
5275 31 Jan 19 nicklas 511       writer.tablePrintData(data);
4026 25 Jul 16 olle 512
5279 05 Feb 19 nicklas 513       int numLines = incaFile.lines.size();
5275 31 Jan 19 nicklas 514       for (IncaLine line : incaFile.lines)
4026 25 Jul 16 olle 515       {
5275 31 Jan 19 nicklas 516         data[0] = line.patientName;
5275 31 Jan 19 nicklas 517         data[1] = line.patIdInca;
5293 12 Feb 19 nicklas 518         data[2] = line.caseId == 0 ? null : (line.hasSpecimen ? "Yes" : "No");
5293 12 Feb 19 nicklas 519         data[3] = line.exclude;
5293 12 Feb 19 nicklas 520         i = 4;
5275 31 Jan 19 nicklas 521         for (Header h : incaFile.headers)
4026 25 Jul 16 olle 522         {
5275 31 Jan 19 nicklas 523           if (h.annotationType == null) continue;
5275 31 Jan 19 nicklas 524           data[i] = line.columns[h.index];
5275 31 Jan 19 nicklas 525           i++;
4026 25 Jul 16 olle 526         }
5275 31 Jan 19 nicklas 527         writer.tablePrintData(data);
5279 05 Feb 19 nicklas 528         if (line.lineNo % 100 == 0)
5279 05 Feb 19 nicklas 529         {
5279 05 Feb 19 nicklas 530           progress.display(100 * line.lineNo / numLines, 
5279 05 Feb 19 nicklas 531             "Writing output CSV file... ("+line.lineNo + " of " + numLines + ")");
5279 05 Feb 19 nicklas 532         }
4026 25 Jul 16 olle 533       }    
4026 25 Jul 16 olle 534
4026 25 Jul 16 olle 535       writer.flush();
5279 05 Feb 19 nicklas 536       out.flush();
5279 05 Feb 19 nicklas 537       progress.display(100, "Output CSV file created with " + numLines + " data lines.");
4026 25 Jul 16 olle 538     }
5295 12 Feb 19 nicklas 539     catch (Exception ex)
4026 25 Jul 16 olle 540     {
4026 25 Jul 16 olle 541       System.out.println(new Date() + " createIncaImportOutputFile(): Could not create writer for outputFilePath = " + outputFilePath + ". Exception e: " + ex);
4026 25 Jul 16 olle 542       //ex.printStackTrace();
4026 25 Jul 16 olle 543     }
5279 05 Feb 19 nicklas 544     finally
5279 05 Feb 19 nicklas 545     {
5279 05 Feb 19 nicklas 546       FileUtil.close(writer);
5279 05 Feb 19 nicklas 547       FileUtil.close(out);
5279 05 Feb 19 nicklas 548     }
4026 25 Jul 16 olle 549   }
4026 25 Jul 16 olle 550
5273 31 Jan 19 nicklas 551
4708 19 Mar 18 nicklas 552
3901 29 Apr 16 olle 553   /**
3951 18 May 16 olle 554    * Returns name to be used for report file.
3951 18 May 16 olle 555    * 
3952 18 May 16 olle 556    * @param reportType String The type of report to get file name for.
3951 18 May 16 olle 557    * @return String name to be used for report file.
3951 18 May 16 olle 558    */
3951 18 May 16 olle 559   private String fetchReportFileName(String reportType)
3951 18 May 16 olle 560   {
3951 18 May 16 olle 561     String reportFileName = null;
3951 18 May 16 olle 562     if (reportType != null)
3951 18 May 16 olle 563     {
3951 18 May 16 olle 564       if (reportType.equals(REPORT_TYPE_IMPORT))
3951 18 May 16 olle 565       {
3951 18 May 16 olle 566         reportFileName = INCA_IMPORT_REPORT_FILENAME;
3951 18 May 16 olle 567       }
4026 25 Jul 16 olle 568       else if (reportType.equals(REPORT_TYPE_IMPORT_OUTPUT_CSV))
4026 25 Jul 16 olle 569       {
4026 25 Jul 16 olle 570         reportFileName = INCA_IMPORT_OUTPUT_CSV_FILENAME;
4026 25 Jul 16 olle 571       }
3963 20 May 16 olle 572       else if (reportType.equals(REPORT_TYPE_STATISTICS))
3963 20 May 16 olle 573       {
3963 20 May 16 olle 574         reportFileName = INCA_STATISTICS_REPORT_FILENAME;
3963 20 May 16 olle 575       }
3980 27 May 16 olle 576       else if (reportType.equals(REPORT_TYPE_STATISTICS_CSV))
3980 27 May 16 olle 577       {
3980 27 May 16 olle 578         reportFileName = INCA_STATISTICS_CSV_FILENAME;
3980 27 May 16 olle 579       }
3951 18 May 16 olle 580     }
3951 18 May 16 olle 581     return reportFileName;
3951 18 May 16 olle 582   }
3951 18 May 16 olle 583
5295 12 Feb 19 nicklas 584   /**
5295 12 Feb 19 nicklas 585     Get the cache key to use for storing the report.
5295 12 Feb 19 nicklas 586     The path is a MD5 value calculated from the current session 
5295 12 Feb 19 nicklas 587     and user id.
5295 12 Feb 19 nicklas 588   */
5279 05 Feb 19 nicklas 589   private String getReportCacheKey(SessionControl sc, String reportType)
5279 05 Feb 19 nicklas 590   {
5332 20 Mar 19 nicklas 591     return "/reggie/inca/" + MD5.getHashString(sc.getId()+"-"+sc.getLoggedInUserId()) + "/" + fetchReportFileName(reportType);
5279 05 Feb 19 nicklas 592   }
3856 18 Apr 16 olle 593
4032 28 Jul 16 olle 594   /**
4032 28 Jul 16 olle 595    * Checks if a report file of right type exists.
4032 28 Jul 16 olle 596    * 
4032 28 Jul 16 olle 597    * @param reportType String The type of report to check for.
4032 28 Jul 16 olle 598    * @return boolean Flag indicating if a report file of right type exists.
4032 28 Jul 16 olle 599    */
5294 12 Feb 19 nicklas 600   boolean checkForReportFile(SessionControl sc, String reportType)
4032 28 Jul 16 olle 601   {
5294 12 Feb 19 nicklas 602     String reportFilePath = getReportCacheKey(sc, reportType);
5294 12 Feb 19 nicklas 603     return Application.getStaticCache().exists(reportFilePath);
4032 28 Jul 16 olle 604   }
4032 28 Jul 16 olle 605
5279 05 Feb 19 nicklas 606   private void createIncaImportReportFile(SessionControl sc, IncaFile incaFile, String checkType)
3856 18 Apr 16 olle 607   {
5279 05 Feb 19 nicklas 608     String reportFilePath = getReportCacheKey(sc, REPORT_TYPE_IMPORT);
5275 31 Jan 19 nicklas 609     Writer report = null;
5279 05 Feb 19 nicklas 610     OutputStream out = null;
5279 05 Feb 19 nicklas 611     
3786 17 Mar 16 olle 612     try
3786 17 Mar 16 olle 613     {
5332 20 Mar 19 nicklas 614       Cipher c = CryptoUtil.createSessionCipher(sc, Cipher.ENCRYPT_MODE, REPORT_TYPE_IMPORT);
5295 12 Feb 19 nicklas 615       out = new CipherOutputStream(Application.getStaticCache().write(reportFilePath, 5), c);
5295 12 Feb 19 nicklas 616       
5279 05 Feb 19 nicklas 617       report = new OutputStreamWriter(out, Charset.forName("UTF-8"));
5293 12 Feb 19 nicklas 618       TableWriter table = new TableWriter(report);
5293 12 Feb 19 nicklas 619
5268 25 Jan 19 nicklas 620       report.write("INCA import report file\n");
5268 25 Jan 19 nicklas 621       report.write("=======================\n");
5270 29 Jan 19 nicklas 622       report.write("INCA data file: " + incaFile.filename + "\n");
5270 29 Jan 19 nicklas 623       report.write("INCA export date: " + Reggie.CONVERTER_DATE_TO_STRING_WITH_SEPARATOR.convert(incaFile.exportDate) + "\n");
5287 08 Feb 19 nicklas 624       report.write("Start time: " + Reggie.CONVERTER_DATETIME_TO_STRING_WITH_SEPARATOR.convert(incaFile.startDate) + "\n");
5287 08 Feb 19 nicklas 625       report.write("End time: " + Reggie.CONVERTER_DATETIME_TO_STRING_WITH_SEPARATOR.convert(incaFile.endDate) + "\n");
5270 29 Jan 19 nicklas 626       if (checkType == null)
5270 29 Jan 19 nicklas 627       {
5270 29 Jan 19 nicklas 628         report.write("Mode: Import\n");
5270 29 Jan 19 nicklas 629         report.write("Checked cases: " + incaFile.numCasesChecked + "\n");
5270 29 Jan 19 nicklas 630         report.write("Updated cases: " + incaFile.numCasesUpdated + "\n");
5273 31 Jan 19 nicklas 631         report.write("Added INCA annotations: " + incaFile.numAnnotations[Change.ADDED.ordinal()] + "\n");
5273 31 Jan 19 nicklas 632         report.write("Modified INCA annotations: " + incaFile.numAnnotations[Change.UPDATED.ordinal()] + "\n");
5273 31 Jan 19 nicklas 633         report.write("Removed INCA annotations: " + incaFile.numAnnotations[Change.DELETED.ordinal()] + "\n");
5273 31 Jan 19 nicklas 634         report.write("Unchanged INCA annotations: " + incaFile.numAnnotations[Change.NO_CHANGE.ordinal()] + "\n");
5270 29 Jan 19 nicklas 635       }
5270 29 Jan 19 nicklas 636       else
5270 29 Jan 19 nicklas 637       {
5270 29 Jan 19 nicklas 638         report.write("Mode: " + checkType + "\n");
5270 29 Jan 19 nicklas 639       }
5268 25 Jan 19 nicklas 640       report.write("\n");
3786 17 Mar 16 olle 641
5273 31 Jan 19 nicklas 642       report.write("Header columns summary\n");
5273 31 Jan 19 nicklas 643       report.write("======================\n");
5270 29 Jan 19 nicklas 644       report.write("Header columns: " + incaFile.headers.length + "\n");
5270 29 Jan 19 nicklas 645       report.write("Missing headers: " + incaFile.missingHeaders.size() + "\n");
5270 29 Jan 19 nicklas 646       report.write("Duplicate headers: "+ incaFile.duplicateHeaders.size() + "\n");
5270 29 Jan 19 nicklas 647       report.write("Unknown headers: " + incaFile.unknownHeaders.size() + "\n");
5270 29 Jan 19 nicklas 648       report.write("Unmapped INCA2 annotations: " + incaFile.unmappedHeaders.size() + "\n");
5268 25 Jan 19 nicklas 649     
5273 31 Jan 19 nicklas 650       report.write("\n");
5273 31 Jan 19 nicklas 651       report.write("Data lines summary\n");
5273 31 Jan 19 nicklas 652       report.write("==================\n");
5273 31 Jan 19 nicklas 653       report.write("Lines of data: " + incaFile.lines.size() + "\n");
5273 31 Jan 19 nicklas 654       report.write("Lines that can be imported: " + (incaFile.lines.size() - incaFile.totalExcludedLines) + "\n");
5273 31 Jan 19 nicklas 655       report.write("Excluded lines: " + incaFile.totalExcludedLines + "\n");
5273 31 Jan 19 nicklas 656       if (incaFile.totalExcludedLines > 0)
5273 31 Jan 19 nicklas 657       {
5273 31 Jan 19 nicklas 658         report.write("   too many columns: " + incaFile.excludedLineCount[ExcludedLine.TOO_MANY_COLUMNS.ordinal()] + "\n");
5273 31 Jan 19 nicklas 659         report.write("   too few columns: " +  incaFile.excludedLineCount[ExcludedLine.TOO_FEW_COLUMNS.ordinal()] + "\n");
5273 31 Jan 19 nicklas 660         report.write("   missing personal number: " +  incaFile.excludedLineCount[ExcludedLine.MISSING_PERSONAL_NO.ordinal()] + "\n");
5273 31 Jan 19 nicklas 661         report.write("   missing or invalid laterality: " +  incaFile.excludedLineCount[ExcludedLine.MISSING_LATERALITY.ordinal()] + "\n");
5273 31 Jan 19 nicklas 662         report.write("   duplicate laterality: " +  incaFile.excludedLineCount[ExcludedLine.DUPLICATE_LATERALITY.ordinal()] + "\n");
5273 31 Jan 19 nicklas 663         report.write("   invalid data value: " +  incaFile.excludedLineCount[ExcludedLine.INVALID_DATA_VALUE.ordinal()] + "\n");
5273 31 Jan 19 nicklas 664         if (incaFile.isFollowUp)
5273 31 Jan 19 nicklas 665         {
5273 31 Jan 19 nicklas 666           report.write("   missing follow-up date: " +  incaFile.excludedLineCount[ExcludedLine.MISSING_FOLLOWUP_DATE.ordinal()] + "\n");
5273 31 Jan 19 nicklas 667           report.write("   duplicate follow-up: " +  incaFile.excludedLineCount[ExcludedLine.DUPLICATE_FOLLOWUP.ordinal()] + "\n");
5273 31 Jan 19 nicklas 668         }
5275 31 Jan 19 nicklas 669         report.write("   patient not found: " +  incaFile.excludedLineCount[ExcludedLine.PATIENT_NOTIN_DATABASE.ordinal()] + "\n");
5273 31 Jan 19 nicklas 670         report.write("   case not found: " +  incaFile.excludedLineCount[ExcludedLine.CASE_NOTIN_DATABASE.ordinal()] + "\n");
5273 31 Jan 19 nicklas 671       }
5273 31 Jan 19 nicklas 672       
5268 25 Jan 19 nicklas 673       if (incaFile.missingHeaders.size() > 0)
5268 25 Jan 19 nicklas 674       {
5268 25 Jan 19 nicklas 675         report.write("\n");
5268 25 Jan 19 nicklas 676         report.write("Missing headers\n");
5268 25 Jan 19 nicklas 677         report.write("----------------\n");
5268 25 Jan 19 nicklas 678         report.write(Values.getString(incaFile.missingHeaders, "\n", true));
5268 25 Jan 19 nicklas 679         report.write("\n");
5268 25 Jan 19 nicklas 680       }
5268 25 Jan 19 nicklas 681       
5268 25 Jan 19 nicklas 682       if (incaFile.duplicateHeaders.size() > 0)
5268 25 Jan 19 nicklas 683       {
5268 25 Jan 19 nicklas 684         report.write("\n");
5268 25 Jan 19 nicklas 685         report.write("Duplicate headers\n");
5268 25 Jan 19 nicklas 686         report.write("-----------------\n");
5268 25 Jan 19 nicklas 687         report.write(Values.getString(incaFile.duplicateHeaders, "\n", true));
5268 25 Jan 19 nicklas 688         report.write("\n");
5268 25 Jan 19 nicklas 689       }
5268 25 Jan 19 nicklas 690       
5268 25 Jan 19 nicklas 691       if (incaFile.unknownHeaders.size() > 0)
5268 25 Jan 19 nicklas 692       {
5268 25 Jan 19 nicklas 693         report.write("\n");
5268 25 Jan 19 nicklas 694         report.write("Unknown headers\n");
5268 25 Jan 19 nicklas 695         report.write("---------------\n");
5293 12 Feb 19 nicklas 696         table.tablePrintData("Column", "Header");
5274 31 Jan 19 nicklas 697         for (Header h : incaFile.headers)
5274 31 Jan 19 nicklas 698         {
5274 31 Jan 19 nicklas 699           if (h.annotationType == null && h.keyColumn == null)
5274 31 Jan 19 nicklas 700           {
5466 04 Jun 19 nicklas 701             table.tablePrintData(h.colNo, h.name);
5274 31 Jan 19 nicklas 702           }
5274 31 Jan 19 nicklas 703         }
5274 31 Jan 19 nicklas 704       }
5274 31 Jan 19 nicklas 705       
5274 31 Jan 19 nicklas 706       if (incaFile.mappedHeaders.size() > 0)
5274 31 Jan 19 nicklas 707       {
5268 25 Jan 19 nicklas 708         report.write("\n");
5274 31 Jan 19 nicklas 709         report.write("Mapped INCA annotations\n");
5274 31 Jan 19 nicklas 710         report.write("-----------------------\n");
5293 12 Feb 19 nicklas 711         table.tablePrintData("Column", "Header", "Annotation");
5274 31 Jan 19 nicklas 712         for (Header h : incaFile.headers)
5274 31 Jan 19 nicklas 713         {
5274 31 Jan 19 nicklas 714           if (h.annotationType != null)
5274 31 Jan 19 nicklas 715           {
5466 04 Jun 19 nicklas 716             table.tablePrintData(h.colNo, h.name, h.annotationType.getName());
5274 31 Jan 19 nicklas 717           }
5274 31 Jan 19 nicklas 718         }
5268 25 Jan 19 nicklas 719       }
5268 25 Jan 19 nicklas 720       
5268 25 Jan 19 nicklas 721       if (incaFile.unmappedHeaders.size() > 0)
5268 25 Jan 19 nicklas 722       {
5268 25 Jan 19 nicklas 723         report.write("\n");
5274 31 Jan 19 nicklas 724         report.write("Unmapped INCA annotations\n");
5274 31 Jan 19 nicklas 725         report.write("-------------------------\n");
5268 25 Jan 19 nicklas 726         report.write(Values.getString(incaFile.unmappedHeaders, "\n", true));
5268 25 Jan 19 nicklas 727         report.write("\n");
5268 25 Jan 19 nicklas 728       }
5273 31 Jan 19 nicklas 729     
5268 25 Jan 19 nicklas 730       if (incaFile.excludedLineCount[ExcludedLine.TOO_MANY_COLUMNS.ordinal()] > 0 || incaFile.excludedLineCount[ExcludedLine.TOO_FEW_COLUMNS.ordinal()] > 0)
5268 25 Jan 19 nicklas 731       {
5273 31 Jan 19 nicklas 732         report.write("\n");
5466 04 Jun 19 nicklas 733         report.write("Lines with incorrect number of columns (expected " +incaFile.headers.length + ")\n");
5466 04 Jun 19 nicklas 734         report.write("-----------------------------------------------------\n");
5293 12 Feb 19 nicklas 735         table.tablePrintData("LineNo", "Columns");
5273 31 Jan 19 nicklas 736         for (IncaLine line : incaFile.lines)
5268 25 Jan 19 nicklas 737         {
5268 25 Jan 19 nicklas 738           if (line.exclude == ExcludedLine.TOO_MANY_COLUMNS || line.exclude == ExcludedLine.TOO_FEW_COLUMNS)
5268 25 Jan 19 nicklas 739           {
5293 12 Feb 19 nicklas 740             table.tablePrintData(line.lineNo, line.columns.length);
5268 25 Jan 19 nicklas 741           }
5268 25 Jan 19 nicklas 742         }
5268 25 Jan 19 nicklas 743         report.write("\n");
5268 25 Jan 19 nicklas 744       }
5268 25 Jan 19 nicklas 745       
5314 28 Feb 19 nicklas 746       // Columns are included in some reports (if they exists)
5314 28 Feb 19 nicklas 747       int diagDateCol = incaFile.keyColumns[KeyColumn.A_DIAG_DAT.ordinal()];
5314 28 Feb 19 nicklas 748       int inrSjhKodCol = incaFile.keyColumns[KeyColumn.A_INR_SJHKOD.ordinal()];
6134 16 Feb 21 nicklas 749       int diagBesDateCol = incaFile.keyColumns[KeyColumn.A_DIAG_BESDAT.ordinal()];
6134 16 Feb 21 nicklas 750       int aPadPrepNrCol = incaFile.keyColumns[KeyColumn.A_PAD_PREPNR.ordinal()];
6134 16 Feb 21 nicklas 751       int aPadPrepArCol = incaFile.keyColumns[KeyColumn.A_PAD_PREPAR.ordinal()];
6134 16 Feb 21 nicklas 752       int opPadPrepNrCol = incaFile.keyColumns[KeyColumn.OP_PAD_PREPNR.ordinal()];
6134 16 Feb 21 nicklas 753       int opPadPrepArCol = incaFile.keyColumns[KeyColumn.OP_PAD_PREPAR.ordinal()];
5314 28 Feb 19 nicklas 754       
5268 25 Jan 19 nicklas 755       // Report lines that has a "personal number" but missing, invalid or duplicate laterality
5273 31 Jan 19 nicklas 756       int sumExcludedLines = incaFile.sumExcludedLines(ExcludedLine.MISSING_LATERALITY, ExcludedLine.DUPLICATE_LATERALITY);
5273 31 Jan 19 nicklas 757       if (sumExcludedLines > 0)
5268 25 Jan 19 nicklas 758       {
5273 31 Jan 19 nicklas 759         report.write("\n");
5268 25 Jan 19 nicklas 760         report.write("Lines with personal number but missing, invalid or duplicate laterality\n");
5314 28 Feb 19 nicklas 761         if (diagDateCol == -1) report.write("Warning: '"+KeyColumn.A_DIAG_DAT.key +"' was not found in the file and will not be reported.");
5314 28 Feb 19 nicklas 762         if (inrSjhKodCol == -1) report.write("Warning: '"+KeyColumn.A_INR_SJHKOD.key +"' was not found in the file and will not be reported.");
6134 16 Feb 21 nicklas 763         if (diagBesDateCol == -1) report.write("Warning: '"+KeyColumn.A_DIAG_BESDAT.key +"' was not found in the file and will not be reported.");
6134 16 Feb 21 nicklas 764         if (aPadPrepNrCol == -1) report.write("Warning: '"+KeyColumn.A_PAD_PREPNR.key +"' was not found in the file and will not be reported.");
6134 16 Feb 21 nicklas 765         if (opPadPrepNrCol == -1) report.write("Warning: '"+KeyColumn.OP_PAD_PREPNR.key +"' was not found in the file and will not be reported.");
5268 25 Jan 19 nicklas 766         report.write("-----------------------------------------------------------------------\n");
6134 16 Feb 21 nicklas 767         table.tablePrintData("LineNo", "PersonalNo", "Laterality", 
6134 16 Feb 21 nicklas 768             KeyColumn.A_DIAG_DAT.key, KeyColumn.A_DIAG_BESDAT.key, KeyColumn.A_INR_SJHKOD.key, 
6134 16 Feb 21 nicklas 769             KeyColumn.A_PAD_PREPNR.key, KeyColumn.A_PAD_PREPAR.key, 
6134 16 Feb 21 nicklas 770             KeyColumn.OP_PAD_PREPNR.key, KeyColumn.OP_PAD_PREPAR.key,
6134 16 Feb 21 nicklas 771             "Flag");
5273 31 Jan 19 nicklas 772         for (IncaLine line : incaFile.lines)
5268 25 Jan 19 nicklas 773         {
6134 16 Feb 21 nicklas 774           String flag = null;
6134 16 Feb 21 nicklas 775           String laterality = null;
5268 25 Jan 19 nicklas 776           if (line.exclude == ExcludedLine.MISSING_LATERALITY)
5268 25 Jan 19 nicklas 777           {
6134 16 Feb 21 nicklas 778             laterality = line.col(incaFile.keyColumns[KeyColumn.LATERALITY.ordinal()]);
6134 16 Feb 21 nicklas 779             flag = laterality == null ? "MISSING_LATERALITY" : "INVALID_LATERALITY";
5268 25 Jan 19 nicklas 780           }
5268 25 Jan 19 nicklas 781           else if (line.exclude == ExcludedLine.DUPLICATE_LATERALITY)
5268 25 Jan 19 nicklas 782           {
6134 16 Feb 21 nicklas 783             flag = "DUPLICATE_LATERALITY";
6134 16 Feb 21 nicklas 784             laterality = line.laterality;
5268 25 Jan 19 nicklas 785           }
6134 16 Feb 21 nicklas 786           if (flag != null)
6134 16 Feb 21 nicklas 787           {
6134 16 Feb 21 nicklas 788             table.tablePrintData(line.lineNo, line.personalNo, laterality, 
6134 16 Feb 21 nicklas 789               line.col(diagDateCol), line.col(diagBesDateCol), line.col(inrSjhKodCol), 
6134 16 Feb 21 nicklas 790               line.col(aPadPrepNrCol), line.col(aPadPrepArCol), line.col(opPadPrepNrCol), line.col(opPadPrepArCol),
6134 16 Feb 21 nicklas 791               flag);
6134 16 Feb 21 nicklas 792           }
5268 25 Jan 19 nicklas 793         }
5268 25 Jan 19 nicklas 794         report.write("\n");
5268 25 Jan 19 nicklas 795       }
5268 25 Jan 19 nicklas 796       
5268 25 Jan 19 nicklas 797       // Report lines that has invalid data values
5268 25 Jan 19 nicklas 798       if (incaFile.excludedLineCount[ExcludedLine.INVALID_DATA_VALUE.ordinal()] > 0)
5268 25 Jan 19 nicklas 799       {
5273 31 Jan 19 nicklas 800         report.write("\n");
5279 05 Feb 19 nicklas 801         report.write("Lines with personal number and invalid data values\n");
5279 05 Feb 19 nicklas 802         report.write("--------------------------------------------------\n");
5293 12 Feb 19 nicklas 803         table.tablePrintData("LineNo", "PersonalNo", "Laterality", "Column", "Value", "Expected type", "Comment");
5273 31 Jan 19 nicklas 804         for (IncaLine line : incaFile.lines)
5268 25 Jan 19 nicklas 805         {
5268 25 Jan 19 nicklas 806           if (line.exclude == ExcludedLine.INVALID_DATA_VALUE)
5268 25 Jan 19 nicklas 807           {
5269 28 Jan 19 nicklas 808             for (int colNo = 0; colNo < incaFile.headers.length; colNo++)
5268 25 Jan 19 nicklas 809             {
5268 25 Jan 19 nicklas 810               Object d = line.data[colNo];
5268 25 Jan 19 nicklas 811               if (d instanceof Exception)
5268 25 Jan 19 nicklas 812               {
5268 25 Jan 19 nicklas 813                 Exception ex = (Exception)d;
5269 28 Jan 19 nicklas 814                 Header h = incaFile.headers[colNo];
5293 12 Feb 19 nicklas 815                 table.tablePrintData(line.lineNo, line.personalNo, line.laterality, 
5293 12 Feb 19 nicklas 816                   h.name, line.columns[colNo], h.annotationType.getValueType().name(), 
5293 12 Feb 19 nicklas 817                   ex.getMessage());
5268 25 Jan 19 nicklas 818               }
5268 25 Jan 19 nicklas 819             }
5268 25 Jan 19 nicklas 820           }
5268 25 Jan 19 nicklas 821         }
5268 25 Jan 19 nicklas 822         report.write("\n");
5268 25 Jan 19 nicklas 823       }
5268 25 Jan 19 nicklas 824
5273 31 Jan 19 nicklas 825       // Report lines that has a "personal number" but no mapped patient or case in the database
5275 31 Jan 19 nicklas 826       sumExcludedLines = incaFile.sumExcludedLines(ExcludedLine.PATIENT_NOTIN_DATABASE, ExcludedLine.CASE_NOTIN_DATABASE);
5273 31 Jan 19 nicklas 827       if (sumExcludedLines > 0)
5273 31 Jan 19 nicklas 828       {
5273 31 Jan 19 nicklas 829         report.write("\n");
5273 31 Jan 19 nicklas 830         report.write("Lines with personal number but no mapped patient or case in the database\n");
5314 28 Feb 19 nicklas 831         if (diagDateCol == -1) report.write("Warning: '"+KeyColumn.A_DIAG_DAT.key +"' was not found in the file and will not be reported.");
5314 28 Feb 19 nicklas 832         if (inrSjhKodCol == -1) report.write("Warning: '"+KeyColumn.A_INR_SJHKOD.key +"' was not found in the file and will not be reported.");
6134 16 Feb 21 nicklas 833         if (diagBesDateCol == -1) report.write("Warning: '"+KeyColumn.A_DIAG_BESDAT.key +"' was not found in the file and will not be reported.");
6134 16 Feb 21 nicklas 834         if (aPadPrepNrCol == -1) report.write("Warning: '"+KeyColumn.A_PAD_PREPNR.key +"' was not found in the file and will not be reported.");
6134 16 Feb 21 nicklas 835         if (opPadPrepNrCol == -1) report.write("Warning: '"+KeyColumn.OP_PAD_PREPNR.key +"' was not found in the file and will not be reported.");
5273 31 Jan 19 nicklas 836         report.write("------------------------------------------------------------------------\n");
6134 16 Feb 21 nicklas 837         table.tablePrintData("LineNo", "PersonalNo", "Laterality", 
6134 16 Feb 21 nicklas 838             KeyColumn.A_DIAG_DAT.key, KeyColumn.A_DIAG_BESDAT.key, KeyColumn.A_INR_SJHKOD.key, 
6134 16 Feb 21 nicklas 839             KeyColumn.A_PAD_PREPNR.key, KeyColumn.A_PAD_PREPAR.key, 
6134 16 Feb 21 nicklas 840             KeyColumn.OP_PAD_PREPNR.key, KeyColumn.OP_PAD_PREPAR.key,
6134 16 Feb 21 nicklas 841             "Flag", "Unmapped items", "Mapped items");
5273 31 Jan 19 nicklas 842         for (IncaLine line : incaFile.lines)
5273 31 Jan 19 nicklas 843         {
5275 31 Jan 19 nicklas 844           if (line.exclude == ExcludedLine.PATIENT_NOTIN_DATABASE)
5273 31 Jan 19 nicklas 845           {
6134 16 Feb 21 nicklas 846             table.tablePrintData(line.lineNo, line.personalNo, line.laterality, 
6134 16 Feb 21 nicklas 847               line.col(diagDateCol), line.col(diagBesDateCol), line.col(inrSjhKodCol), 
6134 16 Feb 21 nicklas 848               line.col(aPadPrepNrCol), line.col(aPadPrepArCol), line.col(opPadPrepNrCol), line.col(opPadPrepArCol),
6134 16 Feb 21 nicklas 849               "PATIENT_NOTIN_DATABASE");
5273 31 Jan 19 nicklas 850           }
5273 31 Jan 19 nicklas 851           else if (line.exclude == ExcludedLine.CASE_NOTIN_DATABASE)
5273 31 Jan 19 nicklas 852           {
5279 05 Feb 19 nicklas 853             String otherMapped = "not found";
5279 05 Feb 19 nicklas 854             if (line.otherCase != null && line.otherCase.caseId != 0)
5279 05 Feb 19 nicklas 855             {
5279 05 Feb 19 nicklas 856               otherMapped = line.otherCase.caseName + " (" + line.otherCase.laterality + "; Line " + line.otherCase.lineNo + ")";
5279 05 Feb 19 nicklas 857             }
5279 05 Feb 19 nicklas 858             String unmapped = "not found";
5279 05 Feb 19 nicklas 859             if (line.unmappedItems != null && line.unmappedItems.size() > 0)
5279 05 Feb 19 nicklas 860             {
5279 05 Feb 19 nicklas 861               unmapped = Values.getString(line.unmappedItems, ", ", true);
5279 05 Feb 19 nicklas 862             }
6134 16 Feb 21 nicklas 863             table.tablePrintData(line.lineNo, line.personalNo, line.laterality, 
6134 16 Feb 21 nicklas 864                 line.col(diagDateCol), line.col(diagBesDateCol), line.col(inrSjhKodCol), 
6134 16 Feb 21 nicklas 865                 line.col(aPadPrepNrCol), line.col(aPadPrepArCol), line.col(opPadPrepNrCol), line.col(opPadPrepArCol),
6134 16 Feb 21 nicklas 866                 "CASE_NOTIN_DATABASE", unmapped, otherMapped);
5273 31 Jan 19 nicklas 867           }
5273 31 Jan 19 nicklas 868         }
5273 31 Jan 19 nicklas 869         report.write("\n");
5273 31 Jan 19 nicklas 870       }
5273 31 Jan 19 nicklas 871       
5268 25 Jan 19 nicklas 872       report.flush();
5295 12 Feb 19 nicklas 873       out.flush();
3786 17 Mar 16 olle 874     }
5295 12 Feb 19 nicklas 875     catch (Exception ex)
3786 17 Mar 16 olle 876     {
3856 18 Apr 16 olle 877       System.out.println(new Date() + " IncaServlet::doPost(): Could not create filewriter for reportFilePath = " + reportFilePath);
3786 17 Mar 16 olle 878     }
5268 25 Jan 19 nicklas 879     finally
5268 25 Jan 19 nicklas 880     {
5268 25 Jan 19 nicklas 881       FileUtil.close(report);
5279 05 Feb 19 nicklas 882       FileUtil.close(out);
5268 25 Jan 19 nicklas 883     }
3786 17 Mar 16 olle 884   }
3786 17 Mar 16 olle 885
3963 20 May 16 olle 886
5290 11 Feb 19 nicklas 887   private void createIncaStatisticsReportFile(SessionControl sc, IncaFile incaFile, String checkType, DateFilter dateFilter, ColumnValueFilter cancerTypeFilter)
3963 20 May 16 olle 888   {
5289 11 Feb 19 nicklas 889     String reportFilePath = getReportCacheKey(sc, REPORT_TYPE_STATISTICS);
5289 11 Feb 19 nicklas 890     Writer report = null;
5289 11 Feb 19 nicklas 891     OutputStream out = null;
5289 11 Feb 19 nicklas 892   
5289 11 Feb 19 nicklas 893     try
5289 11 Feb 19 nicklas 894     {
5332 20 Mar 19 nicklas 895       Cipher c = CryptoUtil.createSessionCipher(sc, Cipher.ENCRYPT_MODE, REPORT_TYPE_STATISTICS);
5295 12 Feb 19 nicklas 896       out = new CipherOutputStream(Application.getStaticCache().write(reportFilePath, 5), c);
5295 12 Feb 19 nicklas 897       
5289 11 Feb 19 nicklas 898       report = new OutputStreamWriter(out, Charset.forName("UTF-8"));
5293 12 Feb 19 nicklas 899       TableWriter table = new TableWriter(report);
5289 11 Feb 19 nicklas 900       
5289 11 Feb 19 nicklas 901       report.write("INCA statistics report file\n");
5289 11 Feb 19 nicklas 902       report.write("===========================\n");
5289 11 Feb 19 nicklas 903       report.write("INCA data file: " + incaFile.filename + "\n");
5289 11 Feb 19 nicklas 904       report.write("Start time: " + Reggie.CONVERTER_DATETIME_TO_STRING_WITH_SEPARATOR.convert(incaFile.startDate) + "\n");
5289 11 Feb 19 nicklas 905       report.write("End time: " + Reggie.CONVERTER_DATETIME_TO_STRING_WITH_SEPARATOR.convert(incaFile.endDate) + "\n");
5290 11 Feb 19 nicklas 906       
5292 12 Feb 19 nicklas 907       boolean simpleMode = "simplestatistics".equals(checkType);
5292 12 Feb 19 nicklas 908       report.write("Mode: " + checkType + "\n");
5290 11 Feb 19 nicklas 909       if (dateFilter != null)
5290 11 Feb 19 nicklas 910       {
5290 11 Feb 19 nicklas 911         report.write("Date filter: " + incaFile.headers[dateFilter.column].name+"\n");
5290 11 Feb 19 nicklas 912         report.write("Start date: " + Reggie.CONVERTER_DATE_TO_STRING_WITH_SEPARATOR.convert(dateFilter.start)+"\n");
5290 11 Feb 19 nicklas 913         report.write("End date: " + Reggie.CONVERTER_DATE_TO_STRING_WITH_SEPARATOR.convert(dateFilter.end)+"\n");
5290 11 Feb 19 nicklas 914       }
5290 11 Feb 19 nicklas 915       if (cancerTypeFilter != null)
5290 11 Feb 19 nicklas 916       {
5290 11 Feb 19 nicklas 917         report.write("Cancer type filter: " + (cancerTypeFilter.value.equals(1) ? "invasive" : "insitu")+"\n");
5290 11 Feb 19 nicklas 918       }
5289 11 Feb 19 nicklas 919       report.write("\n");
5289 11 Feb 19 nicklas 920
5292 12 Feb 19 nicklas 921
5289 11 Feb 19 nicklas 922       report.write("Header columns summary\n");
5289 11 Feb 19 nicklas 923       report.write("======================\n");
5289 11 Feb 19 nicklas 924       report.write("Header columns: " + incaFile.headers.length + "\n");
5289 11 Feb 19 nicklas 925       report.write("Missing headers: " + incaFile.missingHeaders.size() + "\n");
5289 11 Feb 19 nicklas 926       report.write("Missing INCA annotations: " + incaFile.missingAnnotationTypes.size() + "\n");
5289 11 Feb 19 nicklas 927       report.write("Duplicate headers: "+ incaFile.duplicateHeaders.size() + "\n");
5289 11 Feb 19 nicklas 928       report.write("\n");
5289 11 Feb 19 nicklas 929       
5289 11 Feb 19 nicklas 930       report.write("Data lines summary\n");
5289 11 Feb 19 nicklas 931       report.write("==================\n");
5289 11 Feb 19 nicklas 932       report.write("Lines of data: " + incaFile.lines.size() + "\n");
5289 11 Feb 19 nicklas 933       report.write("Lines that can be imported: " + (incaFile.lines.size() - incaFile.totalExcludedLines) + "\n");
5289 11 Feb 19 nicklas 934       report.write("Excluded lines: " + incaFile.totalExcludedLines + "\n");
5289 11 Feb 19 nicklas 935       if (incaFile.totalExcludedLines > 0)
5289 11 Feb 19 nicklas 936       {
5289 11 Feb 19 nicklas 937         report.write("   too many columns: " + incaFile.excludedLineCount[ExcludedLine.TOO_MANY_COLUMNS.ordinal()] + "\n");
5289 11 Feb 19 nicklas 938         report.write("   too few columns: " + incaFile.excludedLineCount[ExcludedLine.TOO_FEW_COLUMNS.ordinal()] + "\n");
5289 11 Feb 19 nicklas 939         report.write("   missing or invalid laterality: " + incaFile.excludedLineCount[ExcludedLine.MISSING_LATERALITY.ordinal()] + "\n");
5289 11 Feb 19 nicklas 940         report.write("   duplicate laterality: " + incaFile.excludedLineCount[ExcludedLine.DUPLICATE_LATERALITY.ordinal()] + "\n");
5289 11 Feb 19 nicklas 941         report.write("   invalid data value: " + incaFile.excludedLineCount[ExcludedLine.INVALID_DATA_VALUE.ordinal()] + "\n");
5289 11 Feb 19 nicklas 942         report.write("   filtered by date: " + incaFile.excludedLineCount[ExcludedLine.FILTERED_BY_DATE.ordinal()] + "\n");
5289 11 Feb 19 nicklas 943         report.write("   filtered by cancer type: " + incaFile.excludedLineCount[ExcludedLine.FILTERED_BY_CANCER_TYPE.ordinal()] + "\n");
5289 11 Feb 19 nicklas 944       }
5290 11 Feb 19 nicklas 945       report.write("\n");
5289 11 Feb 19 nicklas 946
5290 11 Feb 19 nicklas 947       report.write("Statistics summary\n");
5290 11 Feb 19 nicklas 948       report.write("==================\n");
5292 12 Feb 19 nicklas 949       if (simpleMode)
5292 12 Feb 19 nicklas 950       {
5293 12 Feb 19 nicklas 951         table.tablePrintData("Variable", "Value", "Total", "Accrued", "NotAccrued");
5292 12 Feb 19 nicklas 952       }
5292 12 Feb 19 nicklas 953       else
5292 12 Feb 19 nicklas 954       {
5293 12 Feb 19 nicklas 955         table.tablePrintData("Variable", "Value", "Total", "AccruedSpecimen", "AccruedNoSpecimen", "NotAccrued");
5292 12 Feb 19 nicklas 956       }
5290 11 Feb 19 nicklas 957       for (StatisticalVariable var : incaFile.statistics)
5290 11 Feb 19 nicklas 958       {
5290 11 Feb 19 nicklas 959         for (StatisticalGroup group : var.groups)
5290 11 Feb 19 nicklas 960         {
5292 12 Feb 19 nicklas 961           if (simpleMode)
5292 12 Feb 19 nicklas 962           {
5293 12 Feb 19 nicklas 963             table.tablePrintData(var.name, group.name, group.numTotal, group.numAccruedNoSpecimen, group.numNotAccrued);
5292 12 Feb 19 nicklas 964           }
5292 12 Feb 19 nicklas 965           else
5292 12 Feb 19 nicklas 966           {
5293 12 Feb 19 nicklas 967             table.tablePrintData(var.name, group.name, group.numTotal, group.numAccruedSpecimen, group.numAccruedNoSpecimen, group.numNotAccrued);
5292 12 Feb 19 nicklas 968           }
5290 11 Feb 19 nicklas 969         }
5290 11 Feb 19 nicklas 970         report.write("\n");
5290 11 Feb 19 nicklas 971       }
5290 11 Feb 19 nicklas 972       report.write("\n");
5290 11 Feb 19 nicklas 973       
5289 11 Feb 19 nicklas 974       if (incaFile.missingHeaders.size() > 0)
5289 11 Feb 19 nicklas 975       {
5289 11 Feb 19 nicklas 976         report.write("\n");
5289 11 Feb 19 nicklas 977         report.write("Missing headers\n");
5290 11 Feb 19 nicklas 978         report.write("---------------\n");
5289 11 Feb 19 nicklas 979         report.write(Values.getString(incaFile.missingHeaders, "\n", true));
5289 11 Feb 19 nicklas 980         report.write("\n");
5289 11 Feb 19 nicklas 981       }
5289 11 Feb 19 nicklas 982
5289 11 Feb 19 nicklas 983       if (incaFile.duplicateHeaders.size() > 0)
5289 11 Feb 19 nicklas 984       {
5289 11 Feb 19 nicklas 985         report.write("\n");
5289 11 Feb 19 nicklas 986         report.write("Duplicate headers\n");
5289 11 Feb 19 nicklas 987         report.write("-----------------\n");
5289 11 Feb 19 nicklas 988         report.write(Values.getString(incaFile.duplicateHeaders, "\n", true));
5289 11 Feb 19 nicklas 989         report.write("\n");
5289 11 Feb 19 nicklas 990       }
5289 11 Feb 19 nicklas 991       
5289 11 Feb 19 nicklas 992       if (incaFile.missingAnnotationTypes.size() > 0)
5289 11 Feb 19 nicklas 993       {
5289 11 Feb 19 nicklas 994         report.write("\n");
5289 11 Feb 19 nicklas 995         report.write("Missing INCA annotation types\n");
5289 11 Feb 19 nicklas 996         report.write("-----------------------------\n");
5289 11 Feb 19 nicklas 997         report.write(Values.getString(incaFile.missingAnnotationTypes, "\n", true));
5289 11 Feb 19 nicklas 998         report.write("\n");
5289 11 Feb 19 nicklas 999       }
5289 11 Feb 19 nicklas 1000       
5289 11 Feb 19 nicklas 1001       if (incaFile.mappedHeaders.size() > 0)
5289 11 Feb 19 nicklas 1002       {
5289 11 Feb 19 nicklas 1003         report.write("\n");
5289 11 Feb 19 nicklas 1004         report.write("Mapped INCA annotations\n");
5289 11 Feb 19 nicklas 1005         report.write("-----------------------\n");
5293 12 Feb 19 nicklas 1006         table.tablePrintData("Column", "Header", "Annotation");
5289 11 Feb 19 nicklas 1007         for (Header h : incaFile.headers)
5289 11 Feb 19 nicklas 1008         {
5289 11 Feb 19 nicklas 1009           if (h.annotationType != null)
5289 11 Feb 19 nicklas 1010           {
5466 04 Jun 19 nicklas 1011             table.tablePrintData(h.colNo, h.name, h.annotationType.getName());
5289 11 Feb 19 nicklas 1012           }
5289 11 Feb 19 nicklas 1013         }
5289 11 Feb 19 nicklas 1014       }
5289 11 Feb 19 nicklas 1015
5289 11 Feb 19 nicklas 1016       if (incaFile.excludedLineCount[ExcludedLine.TOO_MANY_COLUMNS.ordinal()] > 0 || incaFile.excludedLineCount[ExcludedLine.TOO_FEW_COLUMNS.ordinal()] > 0)
5289 11 Feb 19 nicklas 1017       {
5289 11 Feb 19 nicklas 1018         report.write("\n");
5289 11 Feb 19 nicklas 1019         report.write("Lines with incorrect number of columns\n");
5289 11 Feb 19 nicklas 1020         report.write("--------------------------------------\n");
5293 12 Feb 19 nicklas 1021         table.tablePrintData("LineNo", "Columns");
5289 11 Feb 19 nicklas 1022         for (IncaLine line : incaFile.lines)
5289 11 Feb 19 nicklas 1023         {
5289 11 Feb 19 nicklas 1024           if (line.exclude == ExcludedLine.TOO_MANY_COLUMNS || line.exclude == ExcludedLine.TOO_FEW_COLUMNS)
5289 11 Feb 19 nicklas 1025           {
5293 12 Feb 19 nicklas 1026             table.tablePrintData(line.lineNo, line.columns.length);
5289 11 Feb 19 nicklas 1027           }
5289 11 Feb 19 nicklas 1028         }
5289 11 Feb 19 nicklas 1029         report.write("\n");
5289 11 Feb 19 nicklas 1030       }
5289 11 Feb 19 nicklas 1031       
5289 11 Feb 19 nicklas 1032       // Report lines that has a "personal number" but missing, invalid or duplicate laterality
5289 11 Feb 19 nicklas 1033       int sumExcludedLines = incaFile.sumExcludedLines(ExcludedLine.MISSING_LATERALITY, ExcludedLine.DUPLICATE_LATERALITY);
5289 11 Feb 19 nicklas 1034       if (sumExcludedLines > 0)
5289 11 Feb 19 nicklas 1035       {
5289 11 Feb 19 nicklas 1036         report.write("\n");
5289 11 Feb 19 nicklas 1037         report.write("Lines with with missing, invalid or duplicate laterality\n");
5289 11 Feb 19 nicklas 1038         report.write("--------------------------------------------------------\n");
5293 12 Feb 19 nicklas 1039         table.tablePrintData("LineNo", "PAT_ID", "PersonalNo", "Laterality", "Flag");
5289 11 Feb 19 nicklas 1040         for (IncaLine line : incaFile.lines)
5289 11 Feb 19 nicklas 1041         {
5289 11 Feb 19 nicklas 1042           if (line.exclude == ExcludedLine.MISSING_LATERALITY)
5289 11 Feb 19 nicklas 1043           {
5289 11 Feb 19 nicklas 1044             String lat = line.col(incaFile.keyColumns[KeyColumn.LATERALITY.ordinal()]);
5289 11 Feb 19 nicklas 1045             String flag = lat == null ? "MISSING_LATERALITY" : "INVALID_LATERALITY";
5293 12 Feb 19 nicklas 1046             table.tablePrintData(line.lineNo, line.patIdInca, line.personalNo, lat, flag);
5289 11 Feb 19 nicklas 1047           }
5289 11 Feb 19 nicklas 1048           else if (line.exclude == ExcludedLine.DUPLICATE_LATERALITY)
5289 11 Feb 19 nicklas 1049           {
5293 12 Feb 19 nicklas 1050             table.tablePrintData(line.lineNo, line.patIdInca, line.personalNo, line.laterality, "DUPLICATE_LATERALITY");
5289 11 Feb 19 nicklas 1051           }
5289 11 Feb 19 nicklas 1052         }
5289 11 Feb 19 nicklas 1053         report.write("\n");
5289 11 Feb 19 nicklas 1054       }
5289 11 Feb 19 nicklas 1055       
5289 11 Feb 19 nicklas 1056       // Report lines that has invalid data values
5289 11 Feb 19 nicklas 1057       if (incaFile.excludedLineCount[ExcludedLine.INVALID_DATA_VALUE.ordinal()] > 0)
5289 11 Feb 19 nicklas 1058       {
5289 11 Feb 19 nicklas 1059         report.write("\n");
5289 11 Feb 19 nicklas 1060         report.write("Lines with invalid data values\n");
5289 11 Feb 19 nicklas 1061         report.write("------------------------------\n");
5293 12 Feb 19 nicklas 1062         table.tablePrintData("LineNo", "PAT_ID", "PersonalNo", "Laterality", "Column", "Value", "Expected type", "Comment");
5289 11 Feb 19 nicklas 1063         for (IncaLine line : incaFile.lines)
5289 11 Feb 19 nicklas 1064         {
5289 11 Feb 19 nicklas 1065           if (line.exclude == ExcludedLine.INVALID_DATA_VALUE)
5289 11 Feb 19 nicklas 1066           {
5289 11 Feb 19 nicklas 1067             for (int colNo = 0; colNo < incaFile.headers.length; colNo++)
5289 11 Feb 19 nicklas 1068             {
5289 11 Feb 19 nicklas 1069               Object d = line.data[colNo];
5289 11 Feb 19 nicklas 1070               if (d instanceof Exception)
5289 11 Feb 19 nicklas 1071               {
5289 11 Feb 19 nicklas 1072                 Exception ex = (Exception)d;
5289 11 Feb 19 nicklas 1073                 Header h = incaFile.headers[colNo];
5293 12 Feb 19 nicklas 1074                 table.tablePrintData(line.lineNo, line.patIdInca, line.personalNo, line.laterality, 
5293 12 Feb 19 nicklas 1075                   h.name, line.columns[colNo], h.annotationType.getValueType().name(), 
5293 12 Feb 19 nicklas 1076                   ex.getMessage());
5289 11 Feb 19 nicklas 1077               }
5289 11 Feb 19 nicklas 1078             }
5289 11 Feb 19 nicklas 1079           }
5289 11 Feb 19 nicklas 1080         }
5289 11 Feb 19 nicklas 1081         report.write("\n");
5289 11 Feb 19 nicklas 1082       }
5295 12 Feb 19 nicklas 1083       report.flush();
5295 12 Feb 19 nicklas 1084       out.flush();
5289 11 Feb 19 nicklas 1085     }
5295 12 Feb 19 nicklas 1086     catch (Exception ex)
5289 11 Feb 19 nicklas 1087     {
5289 11 Feb 19 nicklas 1088       System.out.println(new Date() + " IncaStatisticsServlet::doPost(): Could not create filewriter for reportFilePath = " + reportFilePath);
5289 11 Feb 19 nicklas 1089     }
5289 11 Feb 19 nicklas 1090     finally
5289 11 Feb 19 nicklas 1091     {
5289 11 Feb 19 nicklas 1092       FileUtil.close(report);
5289 11 Feb 19 nicklas 1093       FileUtil.close(out);
5289 11 Feb 19 nicklas 1094     }
5289 11 Feb 19 nicklas 1095
3963 20 May 16 olle 1096   }
3963 20 May 16 olle 1097
3963 20 May 16 olle 1098
3836 12 Apr 16 olle 1099   /**
5269 28 Jan 19 nicklas 1100     Class for parsing, processing and collecting data about an INCA
5269 28 Jan 19 nicklas 1101     file.
5269 28 Jan 19 nicklas 1102   */
5269 28 Jan 19 nicklas 1103   static class IncaFile
3836 12 Apr 16 olle 1104   {
5256 21 Jan 19 nicklas 1105     final String filename;
5288 08 Feb 19 nicklas 1106     final boolean forStatistics;
5269 28 Jan 19 nicklas 1107     // Decoder for converting back \n, \r, \t, etc. to newline, tab, etc.
5273 31 Jan 19 nicklas 1108     final TabCrLfEncoderDecoder decoder = new TabCrLfEncoderDecoder(true);
5269 28 Jan 19 nicklas 1109     
5269 28 Jan 19 nicklas 1110     private Header[] headers;
5260 22 Jan 19 nicklas 1111     private int[] keyColumns = new int[KeyColumn.values().length];
3836 12 Apr 16 olle 1112     
5262 24 Jan 19 nicklas 1113     boolean isFollowUp = false;
5260 22 Jan 19 nicklas 1114     
5288 08 Feb 19 nicklas 1115     // Required headers that was not found in the INCA file
5263 24 Jan 19 nicklas 1116     Set<String> missingHeaders = new TreeSet<>();
5288 08 Feb 19 nicklas 1117     // Required headers that has no mathching annotation type in BASE
5288 08 Feb 19 nicklas 1118     Set<String> missingAnnotationTypes = new TreeSet<>();
5260 22 Jan 19 nicklas 1119     // Headers that appear more than once in the INCA file
5263 24 Jan 19 nicklas 1120     Set<String> duplicateHeaders = new TreeSet<>();
5260 22 Jan 19 nicklas 1121     // Headers that are in the INCA file but doesn't have a matching annotation type
5263 24 Jan 19 nicklas 1122     Set<String> unknownHeaders = new TreeSet<>();
5260 22 Jan 19 nicklas 1123     // Annotation types in BASE that are not found in the INCA file
5263 24 Jan 19 nicklas 1124     Set<String> unmappedHeaders = new TreeSet<>();
5274 31 Jan 19 nicklas 1125     // Annotation types in BASE that are found in the INCA file
5274 31 Jan 19 nicklas 1126     Set<String> mappedHeaders = new TreeSet<>();
5260 22 Jan 19 nicklas 1127     
5261 23 Jan 19 nicklas 1128     List<IncaLine> lines = new ArrayList<>();
3836 12 Apr 16 olle 1129     
5263 24 Jan 19 nicklas 1130     int[] excludedLineCount = new int[ExcludedLine.values().length];
5268 25 Jan 19 nicklas 1131     int totalExcludedLines = 0;
5256 21 Jan 19 nicklas 1132     
5273 31 Jan 19 nicklas 1133     Map<String, LinePair> mappedLinePairs = new HashMap<>();
5258 22 Jan 19 nicklas 1134     
5287 08 Feb 19 nicklas 1135     // Some metadata about the import/statistics
5287 08 Feb 19 nicklas 1136     Date startDate;
5287 08 Feb 19 nicklas 1137     Date endDate;
5270 29 Jan 19 nicklas 1138     Date exportDate;
5270 29 Jan 19 nicklas 1139     
5273 31 Jan 19 nicklas 1140     // Statistics about changed cases and annotations
5270 29 Jan 19 nicklas 1141     int numCasesChecked;
5270 29 Jan 19 nicklas 1142     int numCasesUpdated;
5273 31 Jan 19 nicklas 1143     int[] numAnnotations = new int[AnnotationBatcher.Change.values().length];
5270 29 Jan 19 nicklas 1144     
5290 11 Feb 19 nicklas 1145     List<StatisticalVariable> statistics = new ArrayList<>();
5290 11 Feb 19 nicklas 1146     
5269 28 Jan 19 nicklas 1147     /**
5269 28 Jan 19 nicklas 1148       Create a new empty instance.
5269 28 Jan 19 nicklas 1149     */
5288 08 Feb 19 nicklas 1150     IncaFile(String filename, boolean forStatistics)
3836 12 Apr 16 olle 1151     {
3836 12 Apr 16 olle 1152       this.filename = filename;
5288 08 Feb 19 nicklas 1153       this.forStatistics = forStatistics;
5287 08 Feb 19 nicklas 1154       this.startDate = new Date();
5260 22 Jan 19 nicklas 1155       Arrays.fill(keyColumns, -1);
3836 12 Apr 16 olle 1156     }
3836 12 Apr 16 olle 1157     
5269 28 Jan 19 nicklas 1158     /**
5269 28 Jan 19 nicklas 1159       Parse the input stream and start to collect data. 
5269 28 Jan 19 nicklas 1160       The first line is expected to be a header line with columns
5269 28 Jan 19 nicklas 1161       headers separated by {tab} characters. Some checks are made:
5269 28 Jan 19 nicklas 1162       
5269 28 Jan 19 nicklas 1163       - Duplicate headers are detected and stored in 'duplicateHeaders'
5269 28 Jan 19 nicklas 1164       - Required headers are checked and if missing stored in 'missingHeaders'
5269 28 Jan 19 nicklas 1165       
5269 28 Jan 19 nicklas 1166       The rest of the lines are data lines and should have the same number of
5269 28 Jan 19 nicklas 1167       columns as the header line.
5269 28 Jan 19 nicklas 1168     */
5269 28 Jan 19 nicklas 1169     void parse(InputStream in)
5269 28 Jan 19 nicklas 1170       throws IOException
3836 12 Apr 16 olle 1171     {
5269 28 Jan 19 nicklas 1172       BufferedReader reader = null;
5269 28 Jan 19 nicklas 1173       try
5269 28 Jan 19 nicklas 1174       {
5269 28 Jan 19 nicklas 1175         reader = new BufferedReader(new InputStreamReader(in, Charset.forName("UTF-8")));
5269 28 Jan 19 nicklas 1176         
5269 28 Jan 19 nicklas 1177         // First line contains column header names
5269 28 Jan 19 nicklas 1178         String headerLine = reader.readLine();
5269 28 Jan 19 nicklas 1179         setHeaders(headerLine);
5269 28 Jan 19 nicklas 1180       
5269 28 Jan 19 nicklas 1181         String line = reader.readLine();
5269 28 Jan 19 nicklas 1182         while (line != null)
5269 28 Jan 19 nicklas 1183         {
5269 28 Jan 19 nicklas 1184           addLine(line);
5269 28 Jan 19 nicklas 1185           line = reader.readLine();
5269 28 Jan 19 nicklas 1186         }
5269 28 Jan 19 nicklas 1187       }
5269 28 Jan 19 nicklas 1188       finally
5269 28 Jan 19 nicklas 1189       {
5288 08 Feb 19 nicklas 1190         // !Important here that we read to the end of the file. It is not enough to close it!
5288 08 Feb 19 nicklas 1191         // Otherwise, an error will cause the browser to try to re-submit
5288 08 Feb 19 nicklas 1192         // the file a couple of times and in the end only a generic "Error" message
5288 08 Feb 19 nicklas 1193         // without information is displayed
5288 08 Feb 19 nicklas 1194         try
5288 08 Feb 19 nicklas 1195         {
5288 08 Feb 19 nicklas 1196           FileUtil.read(in);
5288 08 Feb 19 nicklas 1197         }
5288 08 Feb 19 nicklas 1198         catch (IOException ex)
5288 08 Feb 19 nicklas 1199         {}
5269 28 Jan 19 nicklas 1200         FileUtil.close(in);
5269 28 Jan 19 nicklas 1201         FileUtil.close(reader);
5269 28 Jan 19 nicklas 1202       }
5269 28 Jan 19 nicklas 1203     }
5269 28 Jan 19 nicklas 1204
5269 28 Jan 19 nicklas 1205     /**
5269 28 Jan 19 nicklas 1206       Parse the header line. See the parse() method for more information.
5269 28 Jan 19 nicklas 1207     */
5269 28 Jan 19 nicklas 1208     private void setHeaders(String headerRow)
5269 28 Jan 19 nicklas 1209     {
5260 22 Jan 19 nicklas 1210       String[] tmp = headerRow.split("\t");
5269 28 Jan 19 nicklas 1211       headers = new Header[tmp.length];
5269 28 Jan 19 nicklas 1212       
5269 28 Jan 19 nicklas 1213       Set<String> names = new HashSet<>(); // To check for duplicates
5269 28 Jan 19 nicklas 1214       for (int colNo = 0; colNo < tmp.length; colNo++)
5256 21 Jan 19 nicklas 1215       {
5269 28 Jan 19 nicklas 1216         Header h = new Header(colNo, tmp[colNo]);
5269 28 Jan 19 nicklas 1217         headers[colNo] = h;
5269 28 Jan 19 nicklas 1218         
5260 22 Jan 19 nicklas 1219         if (h.keyColumn != null)
5256 21 Jan 19 nicklas 1220         {
5269 28 Jan 19 nicklas 1221           // If the 'keyColumn' is set, this header is one of the predifined 'KeyColumn':s
5269 28 Jan 19 nicklas 1222           // We store the 'colNo' for quick access
5269 28 Jan 19 nicklas 1223           keyColumns[h.keyColumn.ordinal()] = colNo;
5260 22 Jan 19 nicklas 1224         }
5260 22 Jan 19 nicklas 1225         if (!names.add(h.name)) 
5260 22 Jan 19 nicklas 1226         {
5260 22 Jan 19 nicklas 1227           duplicateHeaders.add(h.name);
5256 21 Jan 19 nicklas 1228         }
5256 21 Jan 19 nicklas 1229       }
5269 28 Jan 19 nicklas 1230       
5262 24 Jan 19 nicklas 1231       isFollowUp = keyColumns[KeyColumn.FOLLOWUP_DATE.ordinal()] >= 0;
5263 24 Jan 19 nicklas 1232       
5269 28 Jan 19 nicklas 1233       // Check that required columns are present
5288 08 Feb 19 nicklas 1234       checkKeyColumns(false, KeyColumn.PAT_ID, KeyColumn.PERSONAL_NO);
5288 08 Feb 19 nicklas 1235       // Special case for LATERALITY in follow-up files
5263 24 Jan 19 nicklas 1236       if (keyColumns[KeyColumn.LATERALITY.ordinal()] == -1) 
5263 24 Jan 19 nicklas 1237       {
5263 24 Jan 19 nicklas 1238         missingHeaders.add(isFollowUp ? KeyColumn.LATERALITY.alternate : KeyColumn.LATERALITY.key);
5263 24 Jan 19 nicklas 1239       }
3836 12 Apr 16 olle 1240     }
3836 12 Apr 16 olle 1241     
5288 08 Feb 19 nicklas 1242     /**
5288 08 Feb 19 nicklas 1243       Checks that the required key columns are present in the file. 
5288 08 Feb 19 nicklas 1244       Columns that are missing are added to the 'missingHeaders' collections
5288 08 Feb 19 nicklas 1245       or to the 'missingAnnotationTypes' collection.
5288 08 Feb 19 nicklas 1246       @return An array with Header:s matching the KeyColumn input
5288 08 Feb 19 nicklas 1247     */
5288 08 Feb 19 nicklas 1248     Header[] checkKeyColumns(boolean requireAnnotationType, KeyColumn... columns)
5288 08 Feb 19 nicklas 1249     {
5288 08 Feb 19 nicklas 1250       Header[] matched = new Header[columns.length];
5288 08 Feb 19 nicklas 1251       for (int index = 0; index < columns.length; index++)
5288 08 Feb 19 nicklas 1252       {
5288 08 Feb 19 nicklas 1253         KeyColumn kc = columns[index];
5288 08 Feb 19 nicklas 1254         int headerIndex = keyColumns[kc.ordinal()];
5288 08 Feb 19 nicklas 1255         if (headerIndex == -1)
5288 08 Feb 19 nicklas 1256         {
5288 08 Feb 19 nicklas 1257           missingHeaders.add(kc.key);
5288 08 Feb 19 nicklas 1258         }
5288 08 Feb 19 nicklas 1259         else if (requireAnnotationType && headers[headerIndex].annotationType == null)
5288 08 Feb 19 nicklas 1260         {
5288 08 Feb 19 nicklas 1261           missingAnnotationTypes.add("INCA2_"+kc.key);
5288 08 Feb 19 nicklas 1262         }
5288 08 Feb 19 nicklas 1263         else
5288 08 Feb 19 nicklas 1264         {
5288 08 Feb 19 nicklas 1265           matched[index] = headers[headerIndex];
5288 08 Feb 19 nicklas 1266         }
5288 08 Feb 19 nicklas 1267       }
5288 08 Feb 19 nicklas 1268       return matched;
5288 08 Feb 19 nicklas 1269     }
5288 08 Feb 19 nicklas 1270     
5269 28 Jan 19 nicklas 1271     /**
5269 28 Jan 19 nicklas 1272       Add a data line. The text will be split into
5269 28 Jan 19 nicklas 1273       columns and some initial checks are made:
5269 28 Jan 19 nicklas 1274       
5269 28 Jan 19 nicklas 1275       - that the number of columns match the number of headers
5269 28 Jan 19 nicklas 1276       - that required columns have a value (eg. personal no, laterality)
5269 28 Jan 19 nicklas 1277     */
5269 28 Jan 19 nicklas 1278     void addLine(String text)
5269 28 Jan 19 nicklas 1279     {
5466 04 Jun 19 nicklas 1280       IncaLine line = new IncaLine(lines.size()+2, text); // +2 because lineNo=1 is header line
5269 28 Jan 19 nicklas 1281       lines.add(line);      
5269 28 Jan 19 nicklas 1282       line.checkColumns(this);
5269 28 Jan 19 nicklas 1283     }
5269 28 Jan 19 nicklas 1284     
5269 28 Jan 19 nicklas 1285     /**
5269 28 Jan 19 nicklas 1286       Map columns to annotation types. This will populate
5269 28 Jan 19 nicklas 1287       the {@link Header#annotationType} for all headers
5269 28 Jan 19 nicklas 1288       with a name that matches the name of the annotation type
5269 28 Jan 19 nicklas 1289       (after removing the INCA2_ prefix). 
5269 28 Jan 19 nicklas 1290       
5269 28 Jan 19 nicklas 1291       This method will also populate the 'unmappedHeader' with
5269 28 Jan 19 nicklas 1292       the name of all annotation types that hasn't been matched.
5269 28 Jan 19 nicklas 1293       and 'unknownHeaders' with the names of all headers that
5269 28 Jan 19 nicklas 1294       are not mapped to a KeyColumn or AnnotationType.
5269 28 Jan 19 nicklas 1295     */
5289 11 Feb 19 nicklas 1296     void mapAnnotationColumns(List<AnnotationType> annotationTypes, boolean onlyKeyColumns)
5260 22 Jan 19 nicklas 1297     {
5260 22 Jan 19 nicklas 1298       for (AnnotationType at : annotationTypes)
5260 22 Jan 19 nicklas 1299       {
5260 22 Jan 19 nicklas 1300         String nameToMatch = at.getName().replace("INCA2_", "");
5260 22 Jan 19 nicklas 1301         boolean hasMatched = false;
5260 22 Jan 19 nicklas 1302         
5260 22 Jan 19 nicklas 1303         for (Header h : headers)
5260 22 Jan 19 nicklas 1304         {
5289 11 Feb 19 nicklas 1305           if (onlyKeyColumns && h.keyColumn == null) continue;
5289 11 Feb 19 nicklas 1306           
5260 22 Jan 19 nicklas 1307           if (h.name.equals(nameToMatch))
5260 22 Jan 19 nicklas 1308           {
5260 22 Jan 19 nicklas 1309             h.annotationType = at;
5260 22 Jan 19 nicklas 1310             hasMatched = true;
5260 22 Jan 19 nicklas 1311           }
5260 22 Jan 19 nicklas 1312         }
5274 31 Jan 19 nicklas 1313         if (!hasMatched) 
5274 31 Jan 19 nicklas 1314         {
5274 31 Jan 19 nicklas 1315           unmappedHeaders.add(at.getName());
5274 31 Jan 19 nicklas 1316         }
5274 31 Jan 19 nicklas 1317         else
5274 31 Jan 19 nicklas 1318         {
5274 31 Jan 19 nicklas 1319           mappedHeaders.add(at.getName());
5274 31 Jan 19 nicklas 1320         }
5260 22 Jan 19 nicklas 1321       }
5260 22 Jan 19 nicklas 1322       
5269 28 Jan 19 nicklas 1323       // Check all headers if they are mapped to either a 'KeyColumn' or 'AnnotationType'
5260 22 Jan 19 nicklas 1324       for (Header h : headers)
5260 22 Jan 19 nicklas 1325       {
5260 22 Jan 19 nicklas 1326         if (h.annotationType == null && h.keyColumn == null)
5260 22 Jan 19 nicklas 1327         {
5260 22 Jan 19 nicklas 1328           unknownHeaders.add(h.name);
5260 22 Jan 19 nicklas 1329         }
5260 22 Jan 19 nicklas 1330       }
5260 22 Jan 19 nicklas 1331     }
5258 22 Jan 19 nicklas 1332     
5260 22 Jan 19 nicklas 1333     
5269 28 Jan 19 nicklas 1334     /**
5269 28 Jan 19 nicklas 1335       Check for data lines that have identical personal number and laterality.
5269 28 Jan 19 nicklas 1336       This is expected if the INCA file is a follow-up file in which case we 
5269 28 Jan 19 nicklas 1337       also check the follow-up date and choose the line with the latest date.
5273 31 Jan 19 nicklas 1338       It is not expected in a regular file and we exclude both lines.
5273 31 Jan 19 nicklas 1339       
5273 31 Jan 19 nicklas 1340       This method also populates the 'mappedLinePairs' map which is needed
5273 31 Jan 19 nicklas 1341       by the 'doDatabaseMapping' method.
5269 28 Jan 19 nicklas 1342     */
5262 24 Jan 19 nicklas 1343     void doLateralityCheck()
5262 24 Jan 19 nicklas 1344     {
5262 24 Jan 19 nicklas 1345       for (IncaLine line : lines)
5262 24 Jan 19 nicklas 1346       {
5263 24 Jan 19 nicklas 1347         if (line.exclude != null) continue;
5262 24 Jan 19 nicklas 1348         
5292 12 Feb 19 nicklas 1349         String personalId = line.personalNo != null ? line.personalNo : line.patIdInca;
5288 08 Feb 19 nicklas 1350         
5288 08 Feb 19 nicklas 1351         LinePair pair = mappedLinePairs.get(personalId);
5273 31 Jan 19 nicklas 1352         if (pair == null)
5262 24 Jan 19 nicklas 1353         {
5273 31 Jan 19 nicklas 1354           // First time we see this personal number
5273 31 Jan 19 nicklas 1355           pair = new LinePair();
5273 31 Jan 19 nicklas 1356           pair.set(line);
5288 08 Feb 19 nicklas 1357           mappedLinePairs.put(personalId, pair);
5273 31 Jan 19 nicklas 1358         }
5273 31 Jan 19 nicklas 1359         else
5273 31 Jan 19 nicklas 1360         {
5273 31 Jan 19 nicklas 1361           // We have seen this personal number before, see if the other line
5273 31 Jan 19 nicklas 1362           // is the same laterality
5273 31 Jan 19 nicklas 1363           IncaLine other = pair.get(line.laterality);
5273 31 Jan 19 nicklas 1364           if (other == null)
5262 24 Jan 19 nicklas 1365           {
5273 31 Jan 19 nicklas 1366             // The other line is not the same laterality so we can store this line
5273 31 Jan 19 nicklas 1367             pair.set(line);
5273 31 Jan 19 nicklas 1368           }
5273 31 Jan 19 nicklas 1369           else
5273 31 Jan 19 nicklas 1370           {
5273 31 Jan 19 nicklas 1371             // The other line is the same laterality as this one...
5273 31 Jan 19 nicklas 1372             if (isFollowUp)
5262 24 Jan 19 nicklas 1373             {
5273 31 Jan 19 nicklas 1374               // In follow-up files we keep the line with the latest date
5273 31 Jan 19 nicklas 1375               if (line.date.after(other.date))
5273 31 Jan 19 nicklas 1376               {
5273 31 Jan 19 nicklas 1377                 other.exclude = ExcludedLine.DUPLICATE_FOLLOWUP;
5273 31 Jan 19 nicklas 1378                 pair.set(line);
5273 31 Jan 19 nicklas 1379               }
5273 31 Jan 19 nicklas 1380               else
5273 31 Jan 19 nicklas 1381               {
5273 31 Jan 19 nicklas 1382                 line.exclude = ExcludedLine.DUPLICATE_FOLLOWUP;
5273 31 Jan 19 nicklas 1383               }
5262 24 Jan 19 nicklas 1384             }
5262 24 Jan 19 nicklas 1385             else
5262 24 Jan 19 nicklas 1386             {
5273 31 Jan 19 nicklas 1387               // In regular INCA file this is not allowed and we exclude both lines
5273 31 Jan 19 nicklas 1388               other.exclude = ExcludedLine.DUPLICATE_LATERALITY;
5273 31 Jan 19 nicklas 1389               line.exclude = ExcludedLine.DUPLICATE_LATERALITY;
5262 24 Jan 19 nicklas 1390             }
5262 24 Jan 19 nicklas 1391           }
5262 24 Jan 19 nicklas 1392         }
5262 24 Jan 19 nicklas 1393       }
5263 24 Jan 19 nicklas 1394     }
5263 24 Jan 19 nicklas 1395     
5269 28 Jan 19 nicklas 1396     /**
5269 28 Jan 19 nicklas 1397       Check the data values for all columns mapped to annotation types 
5269 28 Jan 19 nicklas 1398       on all lines that are not excluded.
5269 28 Jan 19 nicklas 1399       
5269 28 Jan 19 nicklas 1400       If the value is ok, it is copied to the {@link IncaLine.data} array.
5269 28 Jan 19 nicklas 1401       If the value is not ok, the line is marked as excluded and the
5269 28 Jan 19 nicklas 1402       exception is stored in the 'data' array.
5269 28 Jan 19 nicklas 1403     */
5288 08 Feb 19 nicklas 1404     void doDataCheck(Header[] columnsToCheck)
5267 25 Jan 19 nicklas 1405     {
5288 08 Feb 19 nicklas 1406       if (columnsToCheck == null) columnsToCheck = headers;
5288 08 Feb 19 nicklas 1407       
5267 25 Jan 19 nicklas 1408       for (IncaLine line : lines)
5267 25 Jan 19 nicklas 1409       {
5267 25 Jan 19 nicklas 1410         if (line.exclude != null) continue;
5267 25 Jan 19 nicklas 1411         
5267 25 Jan 19 nicklas 1412         line.data = new Object[line.columns.length];
5267 25 Jan 19 nicklas 1413         
5288 08 Feb 19 nicklas 1414         for (Header h : columnsToCheck)
5267 25 Jan 19 nicklas 1415         {
5288 08 Feb 19 nicklas 1416           if (h != null && h.annotationType != null)
5267 25 Jan 19 nicklas 1417           {
5267 25 Jan 19 nicklas 1418             String s = Values.getStringOrNull(line.columns[h.index]);
5267 25 Jan 19 nicklas 1419             if (s != null)
5267 25 Jan 19 nicklas 1420             {
5267 25 Jan 19 nicklas 1421               try
5267 25 Jan 19 nicklas 1422               {
5267 25 Jan 19 nicklas 1423                 Object value = h.annotationType.getValueType().parseString(s);
5267 25 Jan 19 nicklas 1424                 h.annotationType.validateAnnotationValue(value);
5267 25 Jan 19 nicklas 1425                 line.data[h.index] = value;
5267 25 Jan 19 nicklas 1426               }
5267 25 Jan 19 nicklas 1427               catch (RuntimeException ex)
5267 25 Jan 19 nicklas 1428               {
5268 25 Jan 19 nicklas 1429                 line.data[h.index] = ex;
5267 25 Jan 19 nicklas 1430                 line.exclude = ExcludedLine.INVALID_DATA_VALUE;
5267 25 Jan 19 nicklas 1431               }
5267 25 Jan 19 nicklas 1432             }
5267 25 Jan 19 nicklas 1433           }
5267 25 Jan 19 nicklas 1434         }
5267 25 Jan 19 nicklas 1435       }
5267 25 Jan 19 nicklas 1436     }
5267 25 Jan 19 nicklas 1437     
5273 31 Jan 19 nicklas 1438     /**
5288 08 Feb 19 nicklas 1439       Check all lines that have not already been excluded with the filter.
5288 08 Feb 19 nicklas 1440       Lines that doens't pass are excluded. 
5288 08 Feb 19 nicklas 1441     */
5288 08 Feb 19 nicklas 1442     void doFilter(Filter<IncaLine> filter, ExcludedLine reason)
5288 08 Feb 19 nicklas 1443     {
5288 08 Feb 19 nicklas 1444       for (IncaLine line : lines)
5288 08 Feb 19 nicklas 1445       {
5288 08 Feb 19 nicklas 1446         if (line.exclude != null) continue;
5288 08 Feb 19 nicklas 1447         if (!filter.evaluate(line)) 
5288 08 Feb 19 nicklas 1448         {
5288 08 Feb 19 nicklas 1449           line.exclude = reason;
5288 08 Feb 19 nicklas 1450         }
5288 08 Feb 19 nicklas 1451       }
5288 08 Feb 19 nicklas 1452     }
5288 08 Feb 19 nicklas 1453     
5288 08 Feb 19 nicklas 1454     /**
5273 31 Jan 19 nicklas 1455       Try to find a mathing patient and case in the database for each
5273 31 Jan 19 nicklas 1456       remaining data line. If no match is found found the line is excluded,
5273 31 Jan 19 nicklas 1457       otherwise we store the patientId and caseId together with the data line.
5273 31 Jan 19 nicklas 1458     */
5273 31 Jan 19 nicklas 1459     void doDatabaseMapping(DbControl dc, ProgressReporter progress)
5267 25 Jan 19 nicklas 1460     {
5273 31 Jan 19 nicklas 1461       // Progress reporting
5273 31 Jan 19 nicklas 1462       progress.display(0, "Patient and case mapping...");
5273 31 Jan 19 nicklas 1463       
5273 31 Jan 19 nicklas 1464       // Query to locate cases for a given patient
5273 31 Jan 19 nicklas 1465       ItemQuery<Sample> caseQuery = Sample.getQuery();
5292 12 Feb 19 nicklas 1466       caseQuery.setIncludes(Reggie.INCLUDE_IN_CURRENT_PROJECT);
5273 31 Jan 19 nicklas 1467       Subtype.CASE.addFilter(dc, caseQuery);
5273 31 Jan 19 nicklas 1468       caseQuery.restrict(Restrictions.eq(
5273 31 Jan 19 nicklas 1469         Hql.property("parent"), Hql.entityParameter("patient", Item.BIOSOURCE))
5273 31 Jan 19 nicklas 1470       );
5273 31 Jan 19 nicklas 1471       
5279 05 Feb 19 nicklas 1472       // Query to locate blood items for a given patient -- used if we can't map a data line to an existing case
5279 05 Feb 19 nicklas 1473       ItemQuery<Sample> bloodQuery = Sample.getQuery();
5292 12 Feb 19 nicklas 1474       bloodQuery.setIncludes(Reggie.INCLUDE_IN_CURRENT_PROJECT);
5279 05 Feb 19 nicklas 1475       Subtype.BLOOD.addFilter(dc, bloodQuery);
5279 05 Feb 19 nicklas 1476       bloodQuery.restrict(Restrictions.eq(
5279 05 Feb 19 nicklas 1477         Hql.property("parent"), Hql.entityParameter("patient", Item.BIOSOURCE))
5279 05 Feb 19 nicklas 1478       );
5292 12 Feb 19 nicklas 1479       
5292 12 Feb 19 nicklas 1480       // Load all cases that has a specimen
5292 12 Feb 19 nicklas 1481       ItemQuery<Sample> caseWithSpecimen = Sample.getQuery();
5292 12 Feb 19 nicklas 1482       caseWithSpecimen.setIncludes(Reggie.INCLUDE_IN_CURRENT_PROJECT);
5292 12 Feb 19 nicklas 1483       Subtype.CASE.addFilter(dc, caseWithSpecimen);
5292 12 Feb 19 nicklas 1484       caseWithSpecimen.join(Hql.innerJoin("childCreationEvents", "cce"));
5292 12 Feb 19 nicklas 1485       caseWithSpecimen.join(Hql.innerJoin("cce", "event", "evt"));
5292 12 Feb 19 nicklas 1486       caseWithSpecimen.join(Hql.innerJoin("evt", "bioMaterial", "child"));
5292 12 Feb 19 nicklas 1487       Subtype.SPECIMEN.addFilter(dc, caseWithSpecimen, "child");
5292 12 Feb 19 nicklas 1488       Set<Integer> allCasesWithSpecimen = new HashSet<>();
5292 12 Feb 19 nicklas 1489       allCasesWithSpecimen.addAll(caseWithSpecimen.idList(dc));
5279 05 Feb 19 nicklas 1490
5273 31 Jan 19 nicklas 1491       // Load all patients, this is typically quicker than loading patient one-by-one for a large file
5273 31 Jan 19 nicklas 1492       ItemQuery<BioSource> patientQuery = BioSource.getQuery();
5273 31 Jan 19 nicklas 1493       patientQuery.setIncludes(Reggie.INCLUDE_IN_CURRENT_PROJECT);
5273 31 Jan 19 nicklas 1494       Subtype.PATIENT.addFilter(dc, patientQuery);
5273 31 Jan 19 nicklas 1495       patientQuery.setCacheResult(true);
5273 31 Jan 19 nicklas 1496       patientQuery.setReturnTotalCount(true);
5273 31 Jan 19 nicklas 1497       
5273 31 Jan 19 nicklas 1498       ItemResultIterator<BioSource> it = patientQuery.iterate(dc);
5273 31 Jan 19 nicklas 1499       int numPatients = (int)it.getTotalCount();
5273 31 Jan 19 nicklas 1500       int count = 0;
5273 31 Jan 19 nicklas 1501       while (it.hasNext())
5273 31 Jan 19 nicklas 1502       {
5273 31 Jan 19 nicklas 1503         count++;
5273 31 Jan 19 nicklas 1504         BioSource pat = it.next();
5273 31 Jan 19 nicklas 1505         String personalNo = (String)Annotationtype.PERSONAL_NUMBER.getAnnotationValue(dc, pat);
5273 31 Jan 19 nicklas 1506         
5273 31 Jan 19 nicklas 1507         LinePair pair = mappedLinePairs.get(personalNo);
5273 31 Jan 19 nicklas 1508         if (pair == null) pair = mappedLinePairs.get(pat.getName()); // For debugging with files using PATID instead of PersonalNo
5273 31 Jan 19 nicklas 1509         if (pair != null)
5273 31 Jan 19 nicklas 1510         {
5279 05 Feb 19 nicklas 1511           pair.mapPatient(pat.getId(), pat.getName());
5273 31 Jan 19 nicklas 1512           caseQuery.setEntityParameter("patient", pat);
5279 05 Feb 19 nicklas 1513           Set<String> unmappedItems = new TreeSet<>();
5279 05 Feb 19 nicklas 1514           Set<String> mappedCases = new HashSet<>();
5273 31 Jan 19 nicklas 1515           for (Sample s : caseQuery.list(dc))
5273 31 Jan 19 nicklas 1516           {
5273 31 Jan 19 nicklas 1517             String laterality = (String)Annotationtype.LATERALITY.getAnnotationValue(dc, s);
5292 12 Feb 19 nicklas 1518             boolean hasSpecimen = allCasesWithSpecimen.contains(s.getId());
5292 12 Feb 19 nicklas 1519             boolean mapped = pair.mapCase(laterality, s.getId(), s.getName(), hasSpecimen);
5279 05 Feb 19 nicklas 1520             if (!mapped)
5279 05 Feb 19 nicklas 1521             {
5279 05 Feb 19 nicklas 1522               // A case we have in the database that is not found in the INCA file
6134 16 Feb 21 nicklas 1523               String consentDate = Values.getString(Reggie.CONVERTER_DATE_TO_STRING_WITH_SEPARATOR.convert(
6134 16 Feb 21 nicklas 1524                 (Date)Annotationtype.CONSENT_DATE.getAnnotationValue(dc, s)), "missing");
6134 16 Feb 21 nicklas 1525               unmappedItems.add(s.getName()+" ("+Values.getString(laterality, "Laterality=missing")+"; Consent="+consentDate+")");
5279 05 Feb 19 nicklas 1526             }
5279 05 Feb 19 nicklas 1527             else
5279 05 Feb 19 nicklas 1528             {
5279 05 Feb 19 nicklas 1529               // A case that was matched between the database and the INCA file
5279 05 Feb 19 nicklas 1530               mappedCases.add(s.getName());
5279 05 Feb 19 nicklas 1531             }
5273 31 Jan 19 nicklas 1532           }
5279 05 Feb 19 nicklas 1533           
5292 12 Feb 19 nicklas 1534           if (pair.hasUnmappedCase() && !forStatistics)
5279 05 Feb 19 nicklas 1535           {
5279 05 Feb 19 nicklas 1536             bloodQuery.setEntityParameter("patient", pat);
5279 05 Feb 19 nicklas 1537             for (Sample b : bloodQuery.list(dc))
5279 05 Feb 19 nicklas 1538             {
5279 05 Feb 19 nicklas 1539               if (!mappedCases.contains(b.getName().substring(0,  7)))
5279 05 Feb 19 nicklas 1540               {
6134 16 Feb 21 nicklas 1541                 String consentDate = Values.getString(Reggie.CONVERTER_DATE_TO_STRING_WITH_SEPARATOR.convert(
6134 16 Feb 21 nicklas 1542                   (Date)Annotationtype.CONSENT_DATE.getAnnotationValue(dc, b)), "missing");
6134 16 Feb 21 nicklas 1543                 unmappedItems.add(b.getName() + " (BLOOD; Consent=" + consentDate + ")");
5279 05 Feb 19 nicklas 1544               }
5279 05 Feb 19 nicklas 1545             }
5279 05 Feb 19 nicklas 1546             pair.setUnmappedItems(unmappedItems);
5279 05 Feb 19 nicklas 1547           }
5273 31 Jan 19 nicklas 1548         }
5273 31 Jan 19 nicklas 1549
5273 31 Jan 19 nicklas 1550         // Progress reporting
5273 31 Jan 19 nicklas 1551         if (count % 100 == 0)
5273 31 Jan 19 nicklas 1552         {
5273 31 Jan 19 nicklas 1553           progress.display((100 * count) / numPatients, "Patient and case mapping... (" + count + " of " + numPatients + ")");
5273 31 Jan 19 nicklas 1554         }
5273 31 Jan 19 nicklas 1555       }
5273 31 Jan 19 nicklas 1556       
5273 31 Jan 19 nicklas 1557       // All IncaLines without a patientId and caseId need to be excluded now
5292 12 Feb 19 nicklas 1558       if (!forStatistics)
5267 25 Jan 19 nicklas 1559       {
5292 12 Feb 19 nicklas 1560         for (IncaLine line : lines)
5267 25 Jan 19 nicklas 1561         {
5292 12 Feb 19 nicklas 1562           if (line.exclude != null) continue;
5292 12 Feb 19 nicklas 1563           
5292 12 Feb 19 nicklas 1564           if (line.patientId == 0) 
5292 12 Feb 19 nicklas 1565           {
5292 12 Feb 19 nicklas 1566             line.exclude = ExcludedLine.PATIENT_NOTIN_DATABASE;
5292 12 Feb 19 nicklas 1567           }
5292 12 Feb 19 nicklas 1568           else if (line.caseId == 0)
5292 12 Feb 19 nicklas 1569           {
5292 12 Feb 19 nicklas 1570             line.exclude = ExcludedLine.CASE_NOTIN_DATABASE;
5292 12 Feb 19 nicklas 1571           }
5273 31 Jan 19 nicklas 1572         }
5273 31 Jan 19 nicklas 1573       }
5273 31 Jan 19 nicklas 1574       
5273 31 Jan 19 nicklas 1575       progress.display(100, "Patient and case mapping... (" + count + " of " + numPatients + ")");
5273 31 Jan 19 nicklas 1576     }
5273 31 Jan 19 nicklas 1577     
5273 31 Jan 19 nicklas 1578     /**
5273 31 Jan 19 nicklas 1579       Import INCA annotations in the database. All lines must have been fully checked and
5273 31 Jan 19 nicklas 1580       mapped to the database before this method can be called.
5273 31 Jan 19 nicklas 1581     */
5273 31 Jan 19 nicklas 1582     void doImport(DbControl dc, ProgressReporter progress)
5273 31 Jan 19 nicklas 1583     {
5273 31 Jan 19 nicklas 1584       // Load annotation types
5273 31 Jan 19 nicklas 1585       AnnotationType atIncaExportDate = Annotationtype.INCA_EXPORT_DATE.load(dc);
5273 31 Jan 19 nicklas 1586       AnnotationType atIncaImportDate = Annotationtype.INCA_IMPORT_DATE.load(dc);
5273 31 Jan 19 nicklas 1587       AnnotationType refDate = Annotationtype.REFERENCE_DATE.load(dc);
5273 31 Jan 19 nicklas 1588       AnnotationType refDateSource = Annotationtype.REFERENCE_DATE_SOURCE.load(dc);
5273 31 Jan 19 nicklas 1589       AnnotationType incaDiaDat = Annotationtype.INCA2_a_diag_dat.load(dc);
5273 31 Jan 19 nicklas 1590       
5273 31 Jan 19 nicklas 1591       // Re-create INCA annotation types using new DbControl
5273 31 Jan 19 nicklas 1592       List<AnnotationType> annotationTypes = new ArrayList<AnnotationType>();
5273 31 Jan 19 nicklas 1593       for (Header h : headers)
5273 31 Jan 19 nicklas 1594       {
5273 31 Jan 19 nicklas 1595         if (h.annotationType != null)
5273 31 Jan 19 nicklas 1596         {
5273 31 Jan 19 nicklas 1597           h.annotationType = AnnotationType.getById(dc, h.annotationType.getId());
5273 31 Jan 19 nicklas 1598           annotationTypes.add(h.annotationType);
5273 31 Jan 19 nicklas 1599         }
5273 31 Jan 19 nicklas 1600       }
5273 31 Jan 19 nicklas 1601       annotationTypes.add(atIncaExportDate);
5273 31 Jan 19 nicklas 1602       annotationTypes.add(atIncaImportDate);
5273 31 Jan 19 nicklas 1603       annotationTypes.add(refDate);
5273 31 Jan 19 nicklas 1604       annotationTypes.add(refDateSource);
5273 31 Jan 19 nicklas 1605
5273 31 Jan 19 nicklas 1606       // Use AnnotationBatcher in order to decrease heap memory use and increase commit speed
5273 31 Jan 19 nicklas 1607       AnnotationBatcher batcher = new AnnotationBatcher(dc, Item.SAMPLE);
5273 31 Jan 19 nicklas 1608       batcher.addAnnotationTypes(annotationTypes);
5273 31 Jan 19 nicklas 1609       
5273 31 Jan 19 nicklas 1610       int casesToCheck = lines.size() - totalExcludedLines;
5273 31 Jan 19 nicklas 1611
5273 31 Jan 19 nicklas 1612       for (IncaLine line : lines)
5273 31 Jan 19 nicklas 1613       {
5273 31 Jan 19 nicklas 1614         if (line.exclude != null) continue;
5273 31 Jan 19 nicklas 1615
5273 31 Jan 19 nicklas 1616         numCasesChecked++;
5273 31 Jan 19 nicklas 1617         if (numCasesChecked % 100 == 0)
5273 31 Jan 19 nicklas 1618         {
5273 31 Jan 19 nicklas 1619           progress.display(100 * numCasesChecked / casesToCheck, 
5273 31 Jan 19 nicklas 1620             "Importing... (" + numCasesChecked + " of " + casesToCheck + ")");
5273 31 Jan 19 nicklas 1621         }
5273 31 Jan 19 nicklas 1622         
5273 31 Jan 19 nicklas 1623         Sample theCase = Sample.getById(dc, line.caseId);
5273 31 Jan 19 nicklas 1624         batcher.setCurrentItem(theCase);
5273 31 Jan 19 nicklas 1625         
5273 31 Jan 19 nicklas 1626         boolean entryChanged = false;
5273 31 Jan 19 nicklas 1627         for (Header h : headers)
5273 31 Jan 19 nicklas 1628         {
5273 31 Jan 19 nicklas 1629           if (h.annotationType == null) continue;
5267 25 Jan 19 nicklas 1630           
5273 31 Jan 19 nicklas 1631           Object value = line.data[h.index];
5273 31 Jan 19 nicklas 1632           Change change = batcher.setValue(h.annotationType, value, null, false);
5267 25 Jan 19 nicklas 1633           
5273 31 Jan 19 nicklas 1634           numAnnotations[change.ordinal()]++;    
5273 31 Jan 19 nicklas 1635           if (change != Change.NO_CHANGE)
5267 25 Jan 19 nicklas 1636           {
5273 31 Jan 19 nicklas 1637             entryChanged = true;
5273 31 Jan 19 nicklas 1638             
5273 31 Jan 19 nicklas 1639             // We always update the ReferenceDate annotation if the a_diag_dat annotation has changed
5273 31 Jan 19 nicklas 1640             if (h.annotationType.equals(incaDiaDat))
5267 25 Jan 19 nicklas 1641             {
5273 31 Jan 19 nicklas 1642               batcher.setValue(refDate, value, null, false);
5273 31 Jan 19 nicklas 1643               batcher.setValue(refDateSource, ReferenceDateSource.INCA_DIAGNOSIS_DATE.getTitle(), null, false);
5267 25 Jan 19 nicklas 1644             }
5267 25 Jan 19 nicklas 1645           }
5267 25 Jan 19 nicklas 1646         }
5273 31 Jan 19 nicklas 1647         if (entryChanged) numCasesUpdated++;
5273 31 Jan 19 nicklas 1648         
5273 31 Jan 19 nicklas 1649         // Update INCA export and import dates
5273 31 Jan 19 nicklas 1650         batcher.setValue(atIncaExportDate, exportDate, null, false);
5287 08 Feb 19 nicklas 1651         batcher.setValue(atIncaImportDate, startDate, null, false);
5273 31 Jan 19 nicklas 1652       }
5273 31 Jan 19 nicklas 1653     }
5273 31 Jan 19 nicklas 1654     
5290 11 Feb 19 nicklas 1655     void doStatistics(StatisticalVariable... variables)
5290 11 Feb 19 nicklas 1656     {
5290 11 Feb 19 nicklas 1657       for (StatisticalVariable v : variables)
5290 11 Feb 19 nicklas 1658       {
5290 11 Feb 19 nicklas 1659         statistics.add(v);
5290 11 Feb 19 nicklas 1660       }
5290 11 Feb 19 nicklas 1661       for (IncaLine line : lines)
5290 11 Feb 19 nicklas 1662       {
5290 11 Feb 19 nicklas 1663         if (line.exclude != null) continue;
5290 11 Feb 19 nicklas 1664         
5290 11 Feb 19 nicklas 1665         for (StatisticalVariable v : variables)
5290 11 Feb 19 nicklas 1666         {
5290 11 Feb 19 nicklas 1667           v.update(line);
5290 11 Feb 19 nicklas 1668         }
5290 11 Feb 19 nicklas 1669       }
5290 11 Feb 19 nicklas 1670     }
5290 11 Feb 19 nicklas 1671     
5273 31 Jan 19 nicklas 1672     /**
5273 31 Jan 19 nicklas 1673       Check all lines that have been excluded and summarize this
5273 31 Jan 19 nicklas 1674       in the 'excludedLines' array and 'totalExcludedLines'.
5273 31 Jan 19 nicklas 1675     */
5273 31 Jan 19 nicklas 1676     void summarizeExcludedLines()
5273 31 Jan 19 nicklas 1677     {
5273 31 Jan 19 nicklas 1678       // Reset to 0
5273 31 Jan 19 nicklas 1679       totalExcludedLines = 0;
5273 31 Jan 19 nicklas 1680       for (ExcludedLine ex : ExcludedLine.values())
5273 31 Jan 19 nicklas 1681       {
5273 31 Jan 19 nicklas 1682         excludedLineCount[ex.ordinal()] = 0;
5273 31 Jan 19 nicklas 1683       }
5273 31 Jan 19 nicklas 1684       
5273 31 Jan 19 nicklas 1685       for (IncaLine line : lines)
5273 31 Jan 19 nicklas 1686       {
5273 31 Jan 19 nicklas 1687         if (line.exclude != null) 
5267 25 Jan 19 nicklas 1688         {
5273 31 Jan 19 nicklas 1689           excludedLineCount[line.exclude.ordinal()]++;
5273 31 Jan 19 nicklas 1690           totalExcludedLines++;
5267 25 Jan 19 nicklas 1691         }
5267 25 Jan 19 nicklas 1692       }
5267 25 Jan 19 nicklas 1693     }
5267 25 Jan 19 nicklas 1694     
5273 31 Jan 19 nicklas 1695     /**
5273 31 Jan 19 nicklas 1696       Helper method for counting excluded lines.
5273 31 Jan 19 nicklas 1697     */
5273 31 Jan 19 nicklas 1698     int sumExcludedLines(ExcludedLine... options)
5273 31 Jan 19 nicklas 1699     {
5273 31 Jan 19 nicklas 1700       int sum = 0;
5273 31 Jan 19 nicklas 1701       for (ExcludedLine el : options)
5273 31 Jan 19 nicklas 1702       {
5273 31 Jan 19 nicklas 1703         sum += excludedLineCount[el.ordinal()];
5273 31 Jan 19 nicklas 1704       }
5273 31 Jan 19 nicklas 1705       return sum;
5273 31 Jan 19 nicklas 1706     }
5273 31 Jan 19 nicklas 1707     
5263 24 Jan 19 nicklas 1708     JSONObject getFileInfoInJSON(JSONObject json)
5263 24 Jan 19 nicklas 1709     {
5263 24 Jan 19 nicklas 1710       if (json == null) json = new JSONObject();
5263 24 Jan 19 nicklas 1711       json.put("filename", filename);
5263 24 Jan 19 nicklas 1712       json.put("isFollowup", isFollowUp);
5287 08 Feb 19 nicklas 1713       json.put("startDate", Reggie.CONVERTER_DATETIME_TO_STRING.convert(startDate));
5287 08 Feb 19 nicklas 1714       json.put("endDate", Reggie.CONVERTER_DATETIME_TO_STRING.convert(endDate));
5273 31 Jan 19 nicklas 1715       json.put("exportDate", Reggie.CONVERTER_DATE_TO_STRING.convert(exportDate));
5262 24 Jan 19 nicklas 1716       
5273 31 Jan 19 nicklas 1717       JSONObject jsonImport = new JSONObject();
5273 31 Jan 19 nicklas 1718       jsonImport.put("numCasesChecked", numCasesChecked);
5273 31 Jan 19 nicklas 1719       jsonImport.put("numCasesUpdated", numCasesUpdated);
5273 31 Jan 19 nicklas 1720       for (AnnotationBatcher.Change c : AnnotationBatcher.Change.values())
5273 31 Jan 19 nicklas 1721       {
5273 31 Jan 19 nicklas 1722         jsonImport.put(c.name(), numAnnotations[c.ordinal()]);
5273 31 Jan 19 nicklas 1723       }
5273 31 Jan 19 nicklas 1724       json.put("import", jsonImport);
5273 31 Jan 19 nicklas 1725
5263 24 Jan 19 nicklas 1726       // Information about headers in the selected file
5263 24 Jan 19 nicklas 1727       JSONObject jsonHeaders = new JSONObject();
5269 28 Jan 19 nicklas 1728       jsonHeaders.put("count", headers.length);
5263 24 Jan 19 nicklas 1729       jsonHeaders.put("missing", missingHeaders.size());
5288 08 Feb 19 nicklas 1730       jsonHeaders.put("missingAnnotationTypes", missingAnnotationTypes.size());
5263 24 Jan 19 nicklas 1731       jsonHeaders.put("duplicates", duplicateHeaders.size());
5263 24 Jan 19 nicklas 1732       jsonHeaders.put("unknown", unknownHeaders.size());
5274 31 Jan 19 nicklas 1733       jsonHeaders.put("mapped", mappedHeaders.size());
5268 25 Jan 19 nicklas 1734       jsonHeaders.put("unmapped", unmappedHeaders.size());
5263 24 Jan 19 nicklas 1735       json.put("headers", jsonHeaders);
5263 24 Jan 19 nicklas 1736       
5263 24 Jan 19 nicklas 1737       if (missingHeaders.size() > 0)
5263 24 Jan 19 nicklas 1738       {
5263 24 Jan 19 nicklas 1739         jsonHeaders.put("missingNames", JsonUtil.toArray(missingHeaders, new IdentityConverter<>()));
5263 24 Jan 19 nicklas 1740       }
5288 08 Feb 19 nicklas 1741       if (missingAnnotationTypes.size() > 0)
5288 08 Feb 19 nicklas 1742       {
5288 08 Feb 19 nicklas 1743         jsonHeaders.put("missingAnnotationTypeNames", JsonUtil.toArray(missingAnnotationTypes, new IdentityConverter<>()));
5288 08 Feb 19 nicklas 1744       }
5263 24 Jan 19 nicklas 1745       if (duplicateHeaders.size() > 0)
5263 24 Jan 19 nicklas 1746       {
5263 24 Jan 19 nicklas 1747         jsonHeaders.put("duplicateNames", JsonUtil.toArray(duplicateHeaders, new IdentityConverter<>()));
5263 24 Jan 19 nicklas 1748       }
5274 31 Jan 19 nicklas 1749       if (unknownHeaders.size() > 0)
5274 31 Jan 19 nicklas 1750       {
5274 31 Jan 19 nicklas 1751         jsonHeaders.put("unknownNames", JsonUtil.toArray(unknownHeaders, new IdentityConverter<>()));
5274 31 Jan 19 nicklas 1752       }
5274 31 Jan 19 nicklas 1753       if (mappedHeaders.size() > 0)
5274 31 Jan 19 nicklas 1754       {
5274 31 Jan 19 nicklas 1755         jsonHeaders.put("mappedNames", JsonUtil.toArray(mappedHeaders, new IdentityConverter<>()));
5274 31 Jan 19 nicklas 1756       }
5268 25 Jan 19 nicklas 1757       if (unmappedHeaders.size() > 0)
5268 25 Jan 19 nicklas 1758       {
5268 25 Jan 19 nicklas 1759         jsonHeaders.put("unmappedNames", JsonUtil.toArray(unmappedHeaders, new IdentityConverter<>()));
5268 25 Jan 19 nicklas 1760       }
5263 24 Jan 19 nicklas 1761       
5263 24 Jan 19 nicklas 1762       // Information about data lines in the selected file
5263 24 Jan 19 nicklas 1763       JSONObject jsonData = new JSONObject();
5263 24 Jan 19 nicklas 1764       jsonData.put("count", lines.size());
5263 24 Jan 19 nicklas 1765       for (ExcludedLine ex : ExcludedLine.values())
5263 24 Jan 19 nicklas 1766       {
5263 24 Jan 19 nicklas 1767         jsonData.put(ex.name(), excludedLineCount[ex.ordinal()]);
5263 24 Jan 19 nicklas 1768       }
5273 31 Jan 19 nicklas 1769       jsonData.put("totalExcluded", totalExcludedLines);      
5263 24 Jan 19 nicklas 1770       json.put("data", jsonData);
5263 24 Jan 19 nicklas 1771       
5291 11 Feb 19 nicklas 1772       if (forStatistics)
5291 11 Feb 19 nicklas 1773       {
5291 11 Feb 19 nicklas 1774         // Statistics
5291 11 Feb 19 nicklas 1775         JSONArray jsonStatistics = new JSONArray();
5291 11 Feb 19 nicklas 1776         json.put("statistics", jsonStatistics);
5291 11 Feb 19 nicklas 1777         for (StatisticalVariable var : statistics)
5291 11 Feb 19 nicklas 1778         {
5291 11 Feb 19 nicklas 1779           JSONObject jsonVar = new JSONObject();
5291 11 Feb 19 nicklas 1780           jsonStatistics.add(jsonVar);
5291 11 Feb 19 nicklas 1781           jsonVar.put("name", var.name);
5292 12 Feb 19 nicklas 1782           jsonVar.put("unit", var.unit);
5302 15 Feb 19 nicklas 1783           jsonVar.put("hasNaGroup", var.hasNaGroup);
5302 15 Feb 19 nicklas 1784
5302 15 Feb 19 nicklas 1785           JSONObject jsonNotNa = new JSONObject();
5302 15 Feb 19 nicklas 1786           jsonNotNa.put("total", var.totalNotNa.numTotal);
5302 15 Feb 19 nicklas 1787           jsonNotNa.put("accruedSpecimen", var.totalNotNa.numAccruedSpecimen);
5302 15 Feb 19 nicklas 1788           jsonNotNa.put("accruedNoSpecimen", var.totalNotNa.numAccruedNoSpecimen);
5302 15 Feb 19 nicklas 1789           jsonNotNa.put("notAccrued", var.totalNotNa.numNotAccrued);
5302 15 Feb 19 nicklas 1790           jsonVar.put("notNa", jsonNotNa);
5302 15 Feb 19 nicklas 1791           
5291 11 Feb 19 nicklas 1792           JSONArray jsonGroups = new JSONArray();
5291 11 Feb 19 nicklas 1793           jsonVar.put("groups", jsonGroups);
5291 11 Feb 19 nicklas 1794           for (StatisticalGroup grp : var.groups)
5291 11 Feb 19 nicklas 1795           {
5291 11 Feb 19 nicklas 1796             JSONObject jsonGrp = new JSONObject();
5291 11 Feb 19 nicklas 1797             jsonGrp.put("name", grp.name);
5302 15 Feb 19 nicklas 1798             jsonGrp.put("isNa", grp.isNa);
5291 11 Feb 19 nicklas 1799             jsonGrp.put("total", grp.numTotal);
5292 12 Feb 19 nicklas 1800             jsonGrp.put("accruedSpecimen", grp.numAccruedSpecimen);
5291 11 Feb 19 nicklas 1801             jsonGrp.put("accruedNoSpecimen", grp.numAccruedNoSpecimen);
5291 11 Feb 19 nicklas 1802             jsonGrp.put("notAccrued", grp.numNotAccrued);
5291 11 Feb 19 nicklas 1803             jsonGroups.add(jsonGrp);
5291 11 Feb 19 nicklas 1804           }
5291 11 Feb 19 nicklas 1805         }
5291 11 Feb 19 nicklas 1806       }
5291 11 Feb 19 nicklas 1807       
5263 24 Jan 19 nicklas 1808       return json;
5262 24 Jan 19 nicklas 1809     }
5268 25 Jan 19 nicklas 1810
3836 12 Apr 16 olle 1811   }
3836 12 Apr 16 olle 1812
5269 28 Jan 19 nicklas 1813   /**
5269 28 Jan 19 nicklas 1814     Represents a header column in the INCA file. We use it to
5269 28 Jan 19 nicklas 1815     store information and mapping for the column.
5269 28 Jan 19 nicklas 1816   */
5260 22 Jan 19 nicklas 1817   static class Header
5260 22 Jan 19 nicklas 1818   {
5269 28 Jan 19 nicklas 1819     // The column index of the header, 0-based
5260 22 Jan 19 nicklas 1820     final int index;
5466 04 Jun 19 nicklas 1821     // The column number, 1-based
5466 04 Jun 19 nicklas 1822     final int colNo;
5269 28 Jan 19 nicklas 1823     // The name of the column as it appears in the file
5260 22 Jan 19 nicklas 1824     final String name;
5269 28 Jan 19 nicklas 1825     // Set if this column is one of the pre-defined KeyColumns:s
5260 22 Jan 19 nicklas 1826     final KeyColumn keyColumn;
5269 28 Jan 19 nicklas 1827     // Set if the column has been mapped to an INCA2 annotation type
5269 28 Jan 19 nicklas 1828     // see IncaFile#mapAnnotationColumns
5260 22 Jan 19 nicklas 1829     AnnotationType annotationType;
5260 22 Jan 19 nicklas 1830     
5260 22 Jan 19 nicklas 1831     Header(int index, String name)
5260 22 Jan 19 nicklas 1832     {
5260 22 Jan 19 nicklas 1833       this.index = index;
5466 04 Jun 19 nicklas 1834       this.colNo = index+1;
5260 22 Jan 19 nicklas 1835       this.name = name;
5260 22 Jan 19 nicklas 1836       this.keyColumn = KeyColumn.find(name);
5260 22 Jan 19 nicklas 1837     }
5260 22 Jan 19 nicklas 1838   }
5260 22 Jan 19 nicklas 1839   
3836 12 Apr 16 olle 1840   /**
5260 22 Jan 19 nicklas 1841     Pre-defined key columns in the file that are of special interest.
5258 22 Jan 19 nicklas 1842   */
5260 22 Jan 19 nicklas 1843   static enum KeyColumn
5258 22 Jan 19 nicklas 1844   {
5262 24 Jan 19 nicklas 1845     PAT_ID("PAT_ID", "PATID"),
5262 24 Jan 19 nicklas 1846     PERSONAL_NO("PersonalNo", "PERSNR"),
5262 24 Jan 19 nicklas 1847     LATERALITY("a_pat_sida_Beskrivning", "u_pat_sida_Beskrivning"),
5258 22 Jan 19 nicklas 1848     FOLLOWUP_DATE("u_dat"),
5258 22 Jan 19 nicklas 1849     CANCER_TYPE("op_pad_invasiv_Värde"),
5258 22 Jan 19 nicklas 1850     A_DIAG_DAT("a_diag_dat"),
5258 22 Jan 19 nicklas 1851     OP_KIR_DAT("op_kir_dat"),
5258 22 Jan 19 nicklas 1852     ER_STATUS("op_pad_er_Värde"),
5258 22 Jan 19 nicklas 1853     HER2_STATUS("op_pad_her2ish_Värde"),
5258 22 Jan 19 nicklas 1854     NHG_STATUS("op_pad_nhg_Värde"),
5258 22 Jan 19 nicklas 1855     PGR_STATUS("op_pad_pr_Värde"),
5258 22 Jan 19 nicklas 1856     AGE("a_pat_alder"),
5314 28 Feb 19 nicklas 1857     TUMOR_SIZE("op_pad_invstl"),
6134 16 Feb 21 nicklas 1858     A_INR_SJHKOD("a_inr_sjhkod"),
6134 16 Feb 21 nicklas 1859     A_DIAG_BESDAT("a_diag_besdat"),
6134 16 Feb 21 nicklas 1860     A_PAD_PREPNR("a_pad_prepnr"),
6134 16 Feb 21 nicklas 1861     A_PAD_PREPAR("a_pad_prepar"),
6134 16 Feb 21 nicklas 1862     OP_PAD_PREPNR("op_pad_prepnr"),
6134 16 Feb 21 nicklas 1863     OP_PAD_PREPAR("op_pad_prepar")
5258 22 Jan 19 nicklas 1864     ;
5258 22 Jan 19 nicklas 1865
5258 22 Jan 19 nicklas 1866     private final String key;
5262 24 Jan 19 nicklas 1867     private final String alternate;
5262 24 Jan 19 nicklas 1868     
5260 22 Jan 19 nicklas 1869     KeyColumn(String key)
5258 22 Jan 19 nicklas 1870     {
5262 24 Jan 19 nicklas 1871       this(key, null);
5262 24 Jan 19 nicklas 1872     }
5262 24 Jan 19 nicklas 1873     
5262 24 Jan 19 nicklas 1874     KeyColumn(String key, String alternate)
5262 24 Jan 19 nicklas 1875     {
5258 22 Jan 19 nicklas 1876       this.key = key;
5262 24 Jan 19 nicklas 1877       this.alternate = alternate;
5269 28 Jan 19 nicklas 1878     }    
5260 22 Jan 19 nicklas 1879     
5269 28 Jan 19 nicklas 1880     /**
5269 28 Jan 19 nicklas 1881       Find a KeyColumn based on the name (from the INCA file).
5269 28 Jan 19 nicklas 1882       It will match both primary and alternate names for the column.
5269 28 Jan 19 nicklas 1883     */
5269 28 Jan 19 nicklas 1884     static KeyColumn find(String name)
5260 22 Jan 19 nicklas 1885     {
5269 28 Jan 19 nicklas 1886       if (name == null) return null;
5260 22 Jan 19 nicklas 1887       for (KeyColumn keyCol : values())
5260 22 Jan 19 nicklas 1888       {
5269 28 Jan 19 nicklas 1889         if (name.equals(keyCol.key) || name.equals(keyCol.alternate))
5260 22 Jan 19 nicklas 1890         {
5260 22 Jan 19 nicklas 1891           return keyCol;
5260 22 Jan 19 nicklas 1892         }
5260 22 Jan 19 nicklas 1893       }
5260 22 Jan 19 nicklas 1894       return null;
5260 22 Jan 19 nicklas 1895     }
5258 22 Jan 19 nicklas 1896   }
5258 22 Jan 19 nicklas 1897   
5269 28 Jan 19 nicklas 1898   /**
5269 28 Jan 19 nicklas 1899     Represents a single data line from an INCA file. We store the line
5269 28 Jan 19 nicklas 1900     number and data split into columns. Some important values are extracted
5269 28 Jan 19 nicklas 1901     by the {@link #checkColumns(IncaFile)} method. A line may be 'excluded'
5269 28 Jan 19 nicklas 1902     for several reasons.
5269 28 Jan 19 nicklas 1903   */
5261 23 Jan 19 nicklas 1904   static class IncaLine
5261 23 Jan 19 nicklas 1905   {
5261 23 Jan 19 nicklas 1906     final int lineNo;
5261 23 Jan 19 nicklas 1907     final String[] columns;
5262 24 Jan 19 nicklas 1908
5269 28 Jan 19 nicklas 1909     // Populated in #checkColumns method
5275 31 Jan 19 nicklas 1910     String patIdInca;
5261 23 Jan 19 nicklas 1911     String personalNo;
5262 24 Jan 19 nicklas 1912     String laterality;
5262 24 Jan 19 nicklas 1913     Date date;
5261 23 Jan 19 nicklas 1914     
5269 28 Jan 19 nicklas 1915     // Populated after calling IncaFile.doDataCheck
5267 25 Jan 19 nicklas 1916     Object[] data;
5267 25 Jan 19 nicklas 1917     
5269 28 Jan 19 nicklas 1918     // Populated after calling IncaFile.doDatabaseMapping
5275 31 Jan 19 nicklas 1919     String patientName;
5267 25 Jan 19 nicklas 1920     int patientId;
5279 05 Feb 19 nicklas 1921     String caseName;
5267 25 Jan 19 nicklas 1922     int caseId;
5292 12 Feb 19 nicklas 1923     boolean hasSpecimen;
5267 25 Jan 19 nicklas 1924     
5279 05 Feb 19 nicklas 1925     IncaLine otherCase; // The case with the other laterality for the same patient (if it exists)
5279 05 Feb 19 nicklas 1926     
5279 05 Feb 19 nicklas 1927     // Populated if a case can't be mapped to an existing item in the database
5279 05 Feb 19 nicklas 1928     // This set should include blood and case items for the given patient that
5279 05 Feb 19 nicklas 1929     // was not mapped to any data line in the INCA file
5279 05 Feb 19 nicklas 1930     Set<String> unmappedItems;
5279 05 Feb 19 nicklas 1931     
5263 24 Jan 19 nicklas 1932     ExcludedLine exclude = null;
5262 24 Jan 19 nicklas 1933     
5261 23 Jan 19 nicklas 1934     IncaLine(int lineNo, String line)
5261 23 Jan 19 nicklas 1935     {
5261 23 Jan 19 nicklas 1936       this.lineNo = lineNo;
5467 04 Jun 19 nicklas 1937       this.columns = line.split("\t", -1);
5261 23 Jan 19 nicklas 1938     }
5261 23 Jan 19 nicklas 1939     
5261 23 Jan 19 nicklas 1940     
5269 28 Jan 19 nicklas 1941     /**
5269 28 Jan 19 nicklas 1942       Checks that the number of columns match the number of 
5269 28 Jan 19 nicklas 1943       headers in the INCA file and that required columns have a
5269 28 Jan 19 nicklas 1944       value.
5269 28 Jan 19 nicklas 1945     */
5269 28 Jan 19 nicklas 1946     void checkColumns(IncaFile file)
5261 23 Jan 19 nicklas 1947     {
5269 28 Jan 19 nicklas 1948       if (columns.length > file.headers.length)
5263 24 Jan 19 nicklas 1949       {
5263 24 Jan 19 nicklas 1950         exclude = ExcludedLine.TOO_MANY_COLUMNS;
5263 24 Jan 19 nicklas 1951         return;
5263 24 Jan 19 nicklas 1952       }
5269 28 Jan 19 nicklas 1953       if (columns.length < file.headers.length)
5263 24 Jan 19 nicklas 1954       {
5263 24 Jan 19 nicklas 1955         exclude = ExcludedLine.TOO_FEW_COLUMNS;
5263 24 Jan 19 nicklas 1956         return;
5263 24 Jan 19 nicklas 1957       }
5262 24 Jan 19 nicklas 1958       
5269 28 Jan 19 nicklas 1959       // Decode values
5269 28 Jan 19 nicklas 1960       for (int i = 0; i < columns.length; i++)
5269 28 Jan 19 nicklas 1961       {
5269 28 Jan 19 nicklas 1962         columns[i] = file.decoder.decode(columns[i]);
5269 28 Jan 19 nicklas 1963       }
5269 28 Jan 19 nicklas 1964       
5275 31 Jan 19 nicklas 1965       patIdInca = col(file.keyColumns[KeyColumn.PAT_ID.ordinal()]);
5275 31 Jan 19 nicklas 1966       
5263 24 Jan 19 nicklas 1967       personalNo = col(file.keyColumns[KeyColumn.PERSONAL_NO.ordinal()]);
5263 24 Jan 19 nicklas 1968       if (personalNo == null)
5263 24 Jan 19 nicklas 1969       {
5288 08 Feb 19 nicklas 1970         if (!file.forStatistics)
5288 08 Feb 19 nicklas 1971         {
5288 08 Feb 19 nicklas 1972           // Exclude lines without personal number when importing but not in statistics
5288 08 Feb 19 nicklas 1973           exclude = ExcludedLine.MISSING_PERSONAL_NO;
5288 08 Feb 19 nicklas 1974           return;
5288 08 Feb 19 nicklas 1975         }
5263 24 Jan 19 nicklas 1976       }
5288 08 Feb 19 nicklas 1977       else
5288 08 Feb 19 nicklas 1978       {
5288 08 Feb 19 nicklas 1979         personalNo = this.personalNo.replace("-", "");
5288 08 Feb 19 nicklas 1980       }
5262 24 Jan 19 nicklas 1981       
5267 25 Jan 19 nicklas 1982       String tmp = col(file.keyColumns[KeyColumn.LATERALITY.ordinal()]);
5267 25 Jan 19 nicklas 1983       if ("Höger".equalsIgnoreCase(tmp))
5267 25 Jan 19 nicklas 1984       {
5267 25 Jan 19 nicklas 1985         laterality = "RIGHT";
5267 25 Jan 19 nicklas 1986       }
5267 25 Jan 19 nicklas 1987       else if ("Vänster".equalsIgnoreCase(tmp))
5267 25 Jan 19 nicklas 1988       {
5267 25 Jan 19 nicklas 1989         laterality = "LEFT";
5267 25 Jan 19 nicklas 1990       }
5263 24 Jan 19 nicklas 1991       if (laterality == null)
5262 24 Jan 19 nicklas 1992       {
5263 24 Jan 19 nicklas 1993         exclude = ExcludedLine.MISSING_LATERALITY;
5263 24 Jan 19 nicklas 1994         return;
5262 24 Jan 19 nicklas 1995       }
5262 24 Jan 19 nicklas 1996       
5263 24 Jan 19 nicklas 1997       if (file.isFollowUp) 
5263 24 Jan 19 nicklas 1998       {
5263 24 Jan 19 nicklas 1999         date = Reggie.CONVERTER_STRING_WITH_SEPARATOR_TO_DATE.convert(col(file.keyColumns[KeyColumn.FOLLOWUP_DATE.ordinal()]));
5263 24 Jan 19 nicklas 2000         if (date == null)
5263 24 Jan 19 nicklas 2001         {
5263 24 Jan 19 nicklas 2002           exclude = ExcludedLine.MISSING_FOLLOWUP_DATE;
5263 24 Jan 19 nicklas 2003           return;
5263 24 Jan 19 nicklas 2004         }
5263 24 Jan 19 nicklas 2005       }
5261 23 Jan 19 nicklas 2006     }
5261 23 Jan 19 nicklas 2007     
5261 23 Jan 19 nicklas 2008     private String col(int index)
5261 23 Jan 19 nicklas 2009     {
5273 31 Jan 19 nicklas 2010       return index >= 0 && index < columns.length ? columns[index] : null;
5261 23 Jan 19 nicklas 2011     }
5273 31 Jan 19 nicklas 2012   }
5273 31 Jan 19 nicklas 2013   
5279 05 Feb 19 nicklas 2014   /**
5279 05 Feb 19 nicklas 2015     Represents a pair of INCA data lines for the same patient. One
5279 05 Feb 19 nicklas 2016     line should be the LEFT case and the other line the RIGHT case.
5279 05 Feb 19 nicklas 2017   */
5273 31 Jan 19 nicklas 2018   static class LinePair
5273 31 Jan 19 nicklas 2019   {
5273 31 Jan 19 nicklas 2020     IncaLine leftCase;
5273 31 Jan 19 nicklas 2021     IncaLine rightCase;
5261 23 Jan 19 nicklas 2022     
5279 05 Feb 19 nicklas 2023     LinePair()
5279 05 Feb 19 nicklas 2024     {}
5279 05 Feb 19 nicklas 2025     
5279 05 Feb 19 nicklas 2026     /**
5279 05 Feb 19 nicklas 2027       Sets the LEFT of RIGHT case.
5279 05 Feb 19 nicklas 2028     */
5273 31 Jan 19 nicklas 2029     void set(IncaLine line)
5273 31 Jan 19 nicklas 2030     {
5273 31 Jan 19 nicklas 2031       if ("LEFT".equals(line.laterality))
5273 31 Jan 19 nicklas 2032       {
5273 31 Jan 19 nicklas 2033         leftCase = line;
5273 31 Jan 19 nicklas 2034       }
6124 10 Feb 21 nicklas 2035       else if ("RIGHT".equals(line.laterality))
5273 31 Jan 19 nicklas 2036       {
5273 31 Jan 19 nicklas 2037         rightCase = line;
5273 31 Jan 19 nicklas 2038       }
5273 31 Jan 19 nicklas 2039     }
5273 31 Jan 19 nicklas 2040     
5273 31 Jan 19 nicklas 2041     IncaLine get(String laterality)
5273 31 Jan 19 nicklas 2042     {
6124 10 Feb 21 nicklas 2043       if ("LEFT".equals(laterality)) return leftCase;
6124 10 Feb 21 nicklas 2044       if ("RIGHT".equals(laterality)) return rightCase;
6124 10 Feb 21 nicklas 2045       return null;
5273 31 Jan 19 nicklas 2046     }
5273 31 Jan 19 nicklas 2047     
5279 05 Feb 19 nicklas 2048     /**
5279 05 Feb 19 nicklas 2049       Add patient information to the lines.
5279 05 Feb 19 nicklas 2050       @param patientId Internal ID of the Patient item in the database
5279 05 Feb 19 nicklas 2051       @param patientName Name of the Patient item (=PATNNNN)
5279 05 Feb 19 nicklas 2052     */
5279 05 Feb 19 nicklas 2053     void mapPatient(int patientId, String patientName)
5273 31 Jan 19 nicklas 2054     {
5275 31 Jan 19 nicklas 2055       if (leftCase != null) 
5275 31 Jan 19 nicklas 2056       {
5275 31 Jan 19 nicklas 2057         leftCase.patientId = patientId;
5275 31 Jan 19 nicklas 2058         leftCase.patientName = patientName;
5275 31 Jan 19 nicklas 2059       }
5275 31 Jan 19 nicklas 2060       if (rightCase != null) 
5275 31 Jan 19 nicklas 2061       {
5275 31 Jan 19 nicklas 2062         rightCase.patientId = patientId;
5275 31 Jan 19 nicklas 2063         rightCase.patientName = patientName;
5275 31 Jan 19 nicklas 2064       }
5273 31 Jan 19 nicklas 2065     }
5273 31 Jan 19 nicklas 2066     
5279 05 Feb 19 nicklas 2067     /**
5279 05 Feb 19 nicklas 2068       Set case information to the matching line. Returns TRUE
5279 05 Feb 19 nicklas 2069       if a line was found FALSE if not. It is typically expected that
5279 05 Feb 19 nicklas 2070       we have more cases in our database than what is in the INCA file since
5279 05 Feb 19 nicklas 2071       there is a delay.
5279 05 Feb 19 nicklas 2072       @param laterality Laterlity from the database
5279 05 Feb 19 nicklas 2073       @param caseId Internal ID of the Case item in the database
5292 12 Feb 19 nicklas 2074       @param hasSpecimen Flag that indictes if the case has a specimen item
5279 05 Feb 19 nicklas 2075     */
5292 12 Feb 19 nicklas 2076     boolean mapCase(String laterality, int caseId, String caseName, boolean hasSpecimen)
5273 31 Jan 19 nicklas 2077     {
5279 05 Feb 19 nicklas 2078       boolean wasMapped = false;
6124 10 Feb 21 nicklas 2079       IncaLine caseToMap = get(laterality);
5292 12 Feb 19 nicklas 2080       if (caseToMap != null)
5273 31 Jan 19 nicklas 2081       {
5292 12 Feb 19 nicklas 2082         caseToMap.caseId = caseId;
5292 12 Feb 19 nicklas 2083         caseToMap.caseName = caseName;
5292 12 Feb 19 nicklas 2084         caseToMap.hasSpecimen = hasSpecimen;
5292 12 Feb 19 nicklas 2085         wasMapped = true;
5273 31 Jan 19 nicklas 2086       }
5279 05 Feb 19 nicklas 2087       return wasMapped;
5273 31 Jan 19 nicklas 2088     }
5273 31 Jan 19 nicklas 2089     
5279 05 Feb 19 nicklas 2090     /**
5279 05 Feb 19 nicklas 2091       Checks if all lines have been mapped to a case.
5279 05 Feb 19 nicklas 2092     */
5279 05 Feb 19 nicklas 2093     boolean hasUnmappedCase()
5279 05 Feb 19 nicklas 2094     {
5279 05 Feb 19 nicklas 2095       return leftCase != null && leftCase.caseId == 0 || rightCase != null && rightCase.caseId == 0;
5279 05 Feb 19 nicklas 2096     }
5279 05 Feb 19 nicklas 2097     
5279 05 Feb 19 nicklas 2098     
5279 05 Feb 19 nicklas 2099     /**
5279 05 Feb 19 nicklas 2100       Registers items that we have in the database that was NOT mapped
5279 05 Feb 19 nicklas 2101       to any of the lines in this pair.
5279 05 Feb 19 nicklas 2102     */
5279 05 Feb 19 nicklas 2103     void setUnmappedItems(Set<String> unmappedItems)
5279 05 Feb 19 nicklas 2104     {
5279 05 Feb 19 nicklas 2105       if (leftCase != null && leftCase.caseId == 0)
5279 05 Feb 19 nicklas 2106       {
5279 05 Feb 19 nicklas 2107         leftCase.unmappedItems = unmappedItems;
5279 05 Feb 19 nicklas 2108         leftCase.otherCase = rightCase;
5279 05 Feb 19 nicklas 2109       }
5279 05 Feb 19 nicklas 2110       if (rightCase != null && rightCase.caseId == 0)
5279 05 Feb 19 nicklas 2111       {
5279 05 Feb 19 nicklas 2112          rightCase.unmappedItems = unmappedItems;
5279 05 Feb 19 nicklas 2113          rightCase.otherCase = leftCase;
5279 05 Feb 19 nicklas 2114       }
5279 05 Feb 19 nicklas 2115     }
5279 05 Feb 19 nicklas 2116     
5261 23 Jan 19 nicklas 2117   }
5258 22 Jan 19 nicklas 2118   
5269 28 Jan 19 nicklas 2119   /**
5269 28 Jan 19 nicklas 2120     Reasons for excluding a data line.
5269 28 Jan 19 nicklas 2121   */
5263 24 Jan 19 nicklas 2122   static enum ExcludedLine
5263 24 Jan 19 nicklas 2123   {
5263 24 Jan 19 nicklas 2124     TOO_MANY_COLUMNS,
5263 24 Jan 19 nicklas 2125     TOO_FEW_COLUMNS,
5263 24 Jan 19 nicklas 2126     MISSING_PERSONAL_NO,
5263 24 Jan 19 nicklas 2127     MISSING_LATERALITY,
5263 24 Jan 19 nicklas 2128     MISSING_FOLLOWUP_DATE,
5263 24 Jan 19 nicklas 2129     DUPLICATE_LATERALITY,
5267 25 Jan 19 nicklas 2130     DUPLICATE_FOLLOWUP,
5267 25 Jan 19 nicklas 2131     INVALID_DATA_VALUE,
5275 31 Jan 19 nicklas 2132     PATIENT_NOTIN_DATABASE,
5288 08 Feb 19 nicklas 2133     CASE_NOTIN_DATABASE,
5288 08 Feb 19 nicklas 2134     FILTERED_BY_DATE,
5288 08 Feb 19 nicklas 2135     FILTERED_BY_CANCER_TYPE;
5263 24 Jan 19 nicklas 2136   }
5261 23 Jan 19 nicklas 2137   
5258 22 Jan 19 nicklas 2138   /**
5288 08 Feb 19 nicklas 2139     Filter implementation that accepts lines with a date between
5288 08 Feb 19 nicklas 2140     the start and end dates. Both dates are optional.
5288 08 Feb 19 nicklas 2141   */
5288 08 Feb 19 nicklas 2142   static class DateFilter
5288 08 Feb 19 nicklas 2143     implements Filter<IncaLine>
5288 08 Feb 19 nicklas 2144   {
5288 08 Feb 19 nicklas 2145     
5288 08 Feb 19 nicklas 2146     private final int column;
5288 08 Feb 19 nicklas 2147     private final Date start;
5288 08 Feb 19 nicklas 2148     private final Date end;
5288 08 Feb 19 nicklas 2149     
5288 08 Feb 19 nicklas 2150     DateFilter(int column, Date start, Date end)
5288 08 Feb 19 nicklas 2151     {
5288 08 Feb 19 nicklas 2152       this.column = column;
5288 08 Feb 19 nicklas 2153       this.start = start;
5288 08 Feb 19 nicklas 2154       this.end = end;
5288 08 Feb 19 nicklas 2155     }
5288 08 Feb 19 nicklas 2156
5288 08 Feb 19 nicklas 2157     @Override
5288 08 Feb 19 nicklas 2158     public boolean evaluate(IncaLine line) 
5288 08 Feb 19 nicklas 2159     {
5288 08 Feb 19 nicklas 2160       Date lineDate = (Date)line.data[column];
5288 08 Feb 19 nicklas 2161       return lineDate != null && (start == null || start.before(lineDate)) && (end == null || end.after(lineDate));
5288 08 Feb 19 nicklas 2162     }
5288 08 Feb 19 nicklas 2163   }
5288 08 Feb 19 nicklas 2164   
5288 08 Feb 19 nicklas 2165   /**
5290 11 Feb 19 nicklas 2166     Filter implementation that accepts IncaLine:s where the
5290 11 Feb 19 nicklas 2167     specified column exactly matches the given value.
5288 08 Feb 19 nicklas 2168   */
5290 11 Feb 19 nicklas 2169   static class ColumnValueFilter
5288 08 Feb 19 nicklas 2170     implements Filter<IncaLine>
5288 08 Feb 19 nicklas 2171   {
5288 08 Feb 19 nicklas 2172     
5288 08 Feb 19 nicklas 2173     private final int column;
5290 11 Feb 19 nicklas 2174     private final Object value;
5288 08 Feb 19 nicklas 2175     
5290 11 Feb 19 nicklas 2176     ColumnValueFilter(int column, Object value)
5288 08 Feb 19 nicklas 2177     {
5288 08 Feb 19 nicklas 2178       this.column = column;
5290 11 Feb 19 nicklas 2179       this.value = value;
5288 08 Feb 19 nicklas 2180     }
5290 11 Feb 19 nicklas 2181     
5290 11 Feb 19 nicklas 2182     @Override
5290 11 Feb 19 nicklas 2183     public boolean evaluate(IncaLine line) 
5290 11 Feb 19 nicklas 2184     {
5290 11 Feb 19 nicklas 2185       Object v = line.data[column];
5290 11 Feb 19 nicklas 2186       return value.equals(v);
5290 11 Feb 19 nicklas 2187     }
5290 11 Feb 19 nicklas 2188   }
5288 08 Feb 19 nicklas 2189   
5290 11 Feb 19 nicklas 2190   /**
5290 11 Feb 19 nicklas 2191     Filter implementation that accepts IncaLine:s where
5290 11 Feb 19 nicklas 2192     the specified column has a value between a min and max
5290 11 Feb 19 nicklas 2193     value (inclusive). The filter only works with integer values.
5290 11 Feb 19 nicklas 2194     Use -1 to accept all values.
5290 11 Feb 19 nicklas 2195   */
5290 11 Feb 19 nicklas 2196   static class BetweenFilter
5290 11 Feb 19 nicklas 2197     implements Filter<IncaLine>
5290 11 Feb 19 nicklas 2198   {
5290 11 Feb 19 nicklas 2199     
5290 11 Feb 19 nicklas 2200     private final int column;
5290 11 Feb 19 nicklas 2201     private final int min;
5290 11 Feb 19 nicklas 2202     private final int max;
5290 11 Feb 19 nicklas 2203     
5290 11 Feb 19 nicklas 2204     BetweenFilter(int column, int min, int max)
5290 11 Feb 19 nicklas 2205     {
5290 11 Feb 19 nicklas 2206       this.column = column;
5290 11 Feb 19 nicklas 2207       this.min = min;
5290 11 Feb 19 nicklas 2208       this.max = max;
5290 11 Feb 19 nicklas 2209     }
5290 11 Feb 19 nicklas 2210     
5288 08 Feb 19 nicklas 2211     @Override
5288 08 Feb 19 nicklas 2212     public boolean evaluate(IncaLine line) 
5288 08 Feb 19 nicklas 2213     {
5290 11 Feb 19 nicklas 2214       Integer v = (Integer)line.data[column];
5290 11 Feb 19 nicklas 2215       if (v != null) 
5290 11 Feb 19 nicklas 2216       {
5290 11 Feb 19 nicklas 2217         return (min == -1 || min <= v) && (max == -1 || v <= max);
5290 11 Feb 19 nicklas 2218       }
5290 11 Feb 19 nicklas 2219       return false;
5288 08 Feb 19 nicklas 2220     }
5290 11 Feb 19 nicklas 2221     
5288 08 Feb 19 nicklas 2222   }
5290 11 Feb 19 nicklas 2223   
5290 11 Feb 19 nicklas 2224   /**
5290 11 Feb 19 nicklas 2225     Represents a statistical variable (for example 'ER Status') 
5290 11 Feb 19 nicklas 2226     that we can use for grouping IncaLine entries into subgroups.
5290 11 Feb 19 nicklas 2227     Subgroups are added by any of the 'addXXX' method and are evaluated
5290 11 Feb 19 nicklas 2228     in the same order. An IncaLine entry is counted for the first
5290 11 Feb 19 nicklas 2229     group that matches. Use addNaGroup() to add a 'catch-all' group
5290 11 Feb 19 nicklas 2230     as the last entry.
5290 11 Feb 19 nicklas 2231   */
5290 11 Feb 19 nicklas 2232   static class StatisticalVariable
5290 11 Feb 19 nicklas 2233   {
5290 11 Feb 19 nicklas 2234     
5292 12 Feb 19 nicklas 2235     final String name;
5292 12 Feb 19 nicklas 2236     final int column;
5292 12 Feb 19 nicklas 2237     final String unit;
5292 12 Feb 19 nicklas 2238     final List<StatisticalGroup> groups;
5302 15 Feb 19 nicklas 2239     final StatisticalGroup totalNotNa;
5302 15 Feb 19 nicklas 2240     boolean hasNaGroup;
5290 11 Feb 19 nicklas 2241     
5290 11 Feb 19 nicklas 2242     /**
5290 11 Feb 19 nicklas 2243       Creates a variable with the given name. Values are found in the 
5290 11 Feb 19 nicklas 2244       column with the given index.
5290 11 Feb 19 nicklas 2245     */
5292 12 Feb 19 nicklas 2246     StatisticalVariable(String name, int column, String unit)
5290 11 Feb 19 nicklas 2247     {
5290 11 Feb 19 nicklas 2248       this.name = name;
5290 11 Feb 19 nicklas 2249       this.column = column;
5292 12 Feb 19 nicklas 2250       this.unit = unit;
5290 11 Feb 19 nicklas 2251       this.groups = new ArrayList<>();
5302 15 Feb 19 nicklas 2252       this.totalNotNa = new StatisticalGroup("", false, new StaticFilter<>(true));
5302 15 Feb 19 nicklas 2253       this.hasNaGroup = false;
5290 11 Feb 19 nicklas 2254     }
5290 11 Feb 19 nicklas 2255     
5290 11 Feb 19 nicklas 2256     /**
5290 11 Feb 19 nicklas 2257       Define a subgroup for this variable using the ColumnValueFilter.
5290 11 Feb 19 nicklas 2258       An IncaLine is counted for this subgroup if the column exactly matches
5290 11 Feb 19 nicklas 2259       the given value.
5290 11 Feb 19 nicklas 2260     */
5290 11 Feb 19 nicklas 2261     StatisticalVariable addValueGroup(String name, Object value)
5290 11 Feb 19 nicklas 2262     {
5290 11 Feb 19 nicklas 2263       addGroup(name, new ColumnValueFilter(column, value));
5290 11 Feb 19 nicklas 2264       return this;
5290 11 Feb 19 nicklas 2265     }
5290 11 Feb 19 nicklas 2266     
5290 11 Feb 19 nicklas 2267     /**
5290 11 Feb 19 nicklas 2268       Adds a catch-all subgroup that matches all lines. This should 
5290 11 Feb 19 nicklas 2269       be used as the last subgroup since.
5290 11 Feb 19 nicklas 2270     */
5290 11 Feb 19 nicklas 2271     StatisticalVariable addNaGroup()
5290 11 Feb 19 nicklas 2272     {
5302 15 Feb 19 nicklas 2273       groups.add(new StatisticalGroup("N/A", true, new StaticFilter<>(true)));
5302 15 Feb 19 nicklas 2274       hasNaGroup = true;
5290 11 Feb 19 nicklas 2275       return this;
5290 11 Feb 19 nicklas 2276     }
5290 11 Feb 19 nicklas 2277     
5290 11 Feb 19 nicklas 2278     /**
5290 11 Feb 19 nicklas 2279       Add a subgroup and use the given filter to match.
5290 11 Feb 19 nicklas 2280     */
5290 11 Feb 19 nicklas 2281     StatisticalVariable addGroup(String name, Filter<IncaLine> filter)
5290 11 Feb 19 nicklas 2282     {
5302 15 Feb 19 nicklas 2283       groups.add(new StatisticalGroup(name, false, filter));
5290 11 Feb 19 nicklas 2284       return this;
5290 11 Feb 19 nicklas 2285     }
5290 11 Feb 19 nicklas 2286     
5290 11 Feb 19 nicklas 2287     
5290 11 Feb 19 nicklas 2288     /**
5290 11 Feb 19 nicklas 2289       Update the statistics for this variable with data from the given IncaLine.
5290 11 Feb 19 nicklas 2290       All registered subgroups are evaluated in the order they were registered.
5290 11 Feb 19 nicklas 2291       The line is counted for the first subgroup that matches.
5290 11 Feb 19 nicklas 2292     */
5290 11 Feb 19 nicklas 2293     void update(IncaLine line)
5290 11 Feb 19 nicklas 2294     {
5290 11 Feb 19 nicklas 2295       for (StatisticalGroup g : groups)
5290 11 Feb 19 nicklas 2296       {
5302 15 Feb 19 nicklas 2297         if (g.update(line)) 
5302 15 Feb 19 nicklas 2298         {
5302 15 Feb 19 nicklas 2299           if (!g.isNa) totalNotNa.update(line);
5302 15 Feb 19 nicklas 2300           break;
5302 15 Feb 19 nicklas 2301         }
5290 11 Feb 19 nicklas 2302       }
5290 11 Feb 19 nicklas 2303     }
5290 11 Feb 19 nicklas 2304   }
5290 11 Feb 19 nicklas 2305   
5290 11 Feb 19 nicklas 2306   /**
5290 11 Feb 19 nicklas 2307     Represents a subgroup for a statistical variable. For example
5290 11 Feb 19 nicklas 2308     'ER Status=Positive'. IncaLine entries that matches the registered
5290 11 Feb 19 nicklas 2309     filter is considered to belong to the group.
5290 11 Feb 19 nicklas 2310   */
5290 11 Feb 19 nicklas 2311   static class StatisticalGroup
5290 11 Feb 19 nicklas 2312   {
5290 11 Feb 19 nicklas 2313     final String name;
5302 15 Feb 19 nicklas 2314     final boolean isNa;
5290 11 Feb 19 nicklas 2315     final Filter<IncaLine> filter;
5288 08 Feb 19 nicklas 2316
5290 11 Feb 19 nicklas 2317     int numTotal;
5292 12 Feb 19 nicklas 2318     int numAccruedSpecimen;
5290 11 Feb 19 nicklas 2319     int numAccruedNoSpecimen;
5290 11 Feb 19 nicklas 2320     int numNotAccrued;
5290 11 Feb 19 nicklas 2321     
5290 11 Feb 19 nicklas 2322     /**
5290 11 Feb 19 nicklas 2323       Creates a subgroup with the given name and filter.
5290 11 Feb 19 nicklas 2324     */
5302 15 Feb 19 nicklas 2325     StatisticalGroup(String name, boolean isNa, Filter<IncaLine> filter) 
5290 11 Feb 19 nicklas 2326     {
5290 11 Feb 19 nicklas 2327       this.name = name;
5302 15 Feb 19 nicklas 2328       this.isNa = isNa;
5290 11 Feb 19 nicklas 2329       this.filter = filter;
5290 11 Feb 19 nicklas 2330     }
5290 11 Feb 19 nicklas 2331     
5290 11 Feb 19 nicklas 2332     /**
5290 11 Feb 19 nicklas 2333       Check if the group filter matches the filter and
5290 11 Feb 19 nicklas 2334       update the statistics if it does.
5290 11 Feb 19 nicklas 2335       @return TRUE if the line matched the group, FALSE if not
5290 11 Feb 19 nicklas 2336     */
5290 11 Feb 19 nicklas 2337     boolean update(IncaLine line)
5290 11 Feb 19 nicklas 2338     {
5290 11 Feb 19 nicklas 2339       if (!filter.evaluate(line)) return false;
5290 11 Feb 19 nicklas 2340       
5290 11 Feb 19 nicklas 2341       numTotal++;
5290 11 Feb 19 nicklas 2342       if (line.personalNo != null) 
5290 11 Feb 19 nicklas 2343       {
5292 12 Feb 19 nicklas 2344         if (line.hasSpecimen)
5292 12 Feb 19 nicklas 2345         {
5292 12 Feb 19 nicklas 2346           numAccruedSpecimen++;
5292 12 Feb 19 nicklas 2347         }
5292 12 Feb 19 nicklas 2348         else
5292 12 Feb 19 nicklas 2349         {
5292 12 Feb 19 nicklas 2350           numAccruedNoSpecimen++;
5292 12 Feb 19 nicklas 2351         }
5290 11 Feb 19 nicklas 2352       }
5290 11 Feb 19 nicklas 2353       else
5290 11 Feb 19 nicklas 2354       {
5290 11 Feb 19 nicklas 2355         numNotAccrued++;
5290 11 Feb 19 nicklas 2356       }
5290 11 Feb 19 nicklas 2357       return true;
5290 11 Feb 19 nicklas 2358     }
5290 11 Feb 19 nicklas 2359   }
5288 08 Feb 19 nicklas 2360   
3786 17 Mar 16 olle 2361 }