extensions/net.sf.basedb.reggie/trunk/src/net/sf/basedb/reggie/plugins/FollowupBloodImporter.java

Code
Comments
Other
Rev Date Author Line
5910 17 Apr 20 nicklas 1 package net.sf.basedb.reggie.plugins;
5910 17 Apr 20 nicklas 2
5910 17 Apr 20 nicklas 3 import java.io.IOException;
5910 17 Apr 20 nicklas 4 import java.io.InputStream;
5910 17 Apr 20 nicklas 5 import java.util.ArrayList;
5911 21 Apr 20 nicklas 6 import java.util.Arrays;
5917 23 Apr 20 nicklas 7 import java.util.Calendar;
5911 21 Apr 20 nicklas 8 import java.util.Date;
5911 21 Apr 20 nicklas 9 import java.util.HashMap;
5911 21 Apr 20 nicklas 10 import java.util.HashSet;
5910 17 Apr 20 nicklas 11 import java.util.List;
5911 21 Apr 20 nicklas 12 import java.util.Map;
5911 21 Apr 20 nicklas 13 import java.util.Set;
5910 17 Apr 20 nicklas 14 import java.util.regex.Pattern;
5910 17 Apr 20 nicklas 15
5917 23 Apr 20 nicklas 16 import net.sf.basedb.core.BioMaterialEvent;
5917 23 Apr 20 nicklas 17 import net.sf.basedb.core.BioSource;
5910 17 Apr 20 nicklas 18 import net.sf.basedb.core.DbControl;
5918 24 Apr 20 nicklas 19 import net.sf.basedb.core.Item;
5916 22 Apr 20 nicklas 20 import net.sf.basedb.core.ItemQuery;
5917 23 Apr 20 nicklas 21 import net.sf.basedb.core.ItemSubtype;
5916 22 Apr 20 nicklas 22 import net.sf.basedb.core.ProgressReporter;
5916 22 Apr 20 nicklas 23 import net.sf.basedb.core.Sample;
5916 22 Apr 20 nicklas 24 import net.sf.basedb.core.Type;
5916 22 Apr 20 nicklas 25 import net.sf.basedb.core.query.Annotations;
5916 22 Apr 20 nicklas 26 import net.sf.basedb.core.query.Expressions;
5916 22 Apr 20 nicklas 27 import net.sf.basedb.core.query.Hql;
5916 22 Apr 20 nicklas 28 import net.sf.basedb.core.query.Restrictions;
5916 22 Apr 20 nicklas 29 import net.sf.basedb.reggie.Reggie;
5916 22 Apr 20 nicklas 30 import net.sf.basedb.reggie.dao.Annotationtype;
5916 22 Apr 20 nicklas 31 import net.sf.basedb.reggie.dao.Blood;
5918 24 Apr 20 nicklas 32 import net.sf.basedb.reggie.dao.Consent;
5918 24 Apr 20 nicklas 33 import net.sf.basedb.reggie.dao.ReggieItem;
5916 22 Apr 20 nicklas 34 import net.sf.basedb.reggie.dao.Subtype;
5911 21 Apr 20 nicklas 35 import net.sf.basedb.util.Values;
5910 17 Apr 20 nicklas 36 import net.sf.basedb.util.excel.XlsxToCsvUtil;
5911 21 Apr 20 nicklas 37 import net.sf.basedb.util.formatter.DateFormatter;
5911 21 Apr 20 nicklas 38 import net.sf.basedb.util.formatter.Formatter;
5910 17 Apr 20 nicklas 39 import net.sf.basedb.util.parser.FlatFileParser;
5910 17 Apr 20 nicklas 40 import net.sf.basedb.util.parser.FlatFileParser.LineType;
5910 17 Apr 20 nicklas 41 import net.sf.basedb.util.parser.Mapper;
5910 17 Apr 20 nicklas 42
5910 17 Apr 20 nicklas 43 /**
5910 17 Apr 20 nicklas 44   Plug-in for importing follow-up blood items.
5910 17 Apr 20 nicklas 45   @author nicklas
5910 17 Apr 20 nicklas 46   @since 4.27
5910 17 Apr 20 nicklas 47 */
5910 17 Apr 20 nicklas 48 public class FollowupBloodImporter
5910 17 Apr 20 nicklas 49 {
5910 17 Apr 20 nicklas 50
5910 17 Apr 20 nicklas 51   private final List<String> errorMessages;
5910 17 Apr 20 nicklas 52   private final List<String> warningMessages;
5910 17 Apr 20 nicklas 53   private List<String> worksheets;
5910 17 Apr 20 nicklas 54   private String parsedWorksheet;
5910 17 Apr 20 nicklas 55
5911 21 Apr 20 nicklas 56   private Set<String> allowedBloodSampleValues;
5911 21 Apr 20 nicklas 57   private Pattern rccIdPattern;
6801 11 Aug 22 nicklas 58   private Pattern rccIdPatternOld;
5911 21 Apr 20 nicklas 59   private Map<String, Integer> uniqueItems;
5910 17 Apr 20 nicklas 60   private int numEntries;
5911 21 Apr 20 nicklas 61   private int numValid;
5916 22 Apr 20 nicklas 62   private int numExisting;
5916 22 Apr 20 nicklas 63   private int numCreated;
5910 17 Apr 20 nicklas 64   
5910 17 Apr 20 nicklas 65   public FollowupBloodImporter()
5910 17 Apr 20 nicklas 66   {
5910 17 Apr 20 nicklas 67     this.errorMessages = new ArrayList<String>();
5910 17 Apr 20 nicklas 68     this.warningMessages = new ArrayList<String>();
5910 17 Apr 20 nicklas 69   }
5910 17 Apr 20 nicklas 70   
5910 17 Apr 20 nicklas 71   public void addWarningMessage(String msg)
5910 17 Apr 20 nicklas 72   {
5910 17 Apr 20 nicklas 73     this.warningMessages.add("[Warning] " + msg);
5910 17 Apr 20 nicklas 74   }
5910 17 Apr 20 nicklas 75   
5910 17 Apr 20 nicklas 76   public List<String> getWarningMessages()
5910 17 Apr 20 nicklas 77   {
5910 17 Apr 20 nicklas 78     return warningMessages;
5910 17 Apr 20 nicklas 79   }
5910 17 Apr 20 nicklas 80   
5910 17 Apr 20 nicklas 81   public boolean hasWarning()
5910 17 Apr 20 nicklas 82   {
5910 17 Apr 20 nicklas 83     return warningMessages.size() > 0;
5910 17 Apr 20 nicklas 84   }
5910 17 Apr 20 nicklas 85
5910 17 Apr 20 nicklas 86   public void addErrorMessage(String msg)
5910 17 Apr 20 nicklas 87   {
5910 17 Apr 20 nicklas 88     this.errorMessages.add("[Error] " + msg);
5910 17 Apr 20 nicklas 89   }
5910 17 Apr 20 nicklas 90   
5910 17 Apr 20 nicklas 91   public List<String> getErrorMessages()
5910 17 Apr 20 nicklas 92   {
5910 17 Apr 20 nicklas 93     return errorMessages;
5910 17 Apr 20 nicklas 94   }
5910 17 Apr 20 nicklas 95
5910 17 Apr 20 nicklas 96   public boolean hasError()
5910 17 Apr 20 nicklas 97   {
5910 17 Apr 20 nicklas 98     return errorMessages.size() > 0;
5910 17 Apr 20 nicklas 99   }
5910 17 Apr 20 nicklas 100   
5910 17 Apr 20 nicklas 101   public List<String> getWorksheets()
5910 17 Apr 20 nicklas 102   {
5910 17 Apr 20 nicklas 103     return worksheets;
5910 17 Apr 20 nicklas 104   }
5910 17 Apr 20 nicklas 105   
5910 17 Apr 20 nicklas 106   public String getParsedWorksheet()
5910 17 Apr 20 nicklas 107   {
5910 17 Apr 20 nicklas 108     return parsedWorksheet;
5910 17 Apr 20 nicklas 109   }
5910 17 Apr 20 nicklas 110
5910 17 Apr 20 nicklas 111   public int getNumEntries()
5910 17 Apr 20 nicklas 112   {
5910 17 Apr 20 nicklas 113     return numEntries;
5910 17 Apr 20 nicklas 114   }
5910 17 Apr 20 nicklas 115   
5911 21 Apr 20 nicklas 116   public int getNumValidEntries()
5911 21 Apr 20 nicklas 117   {
5911 21 Apr 20 nicklas 118     return numValid;
5911 21 Apr 20 nicklas 119   }
5911 21 Apr 20 nicklas 120   
5916 22 Apr 20 nicklas 121   public int getNumCreated()
5916 22 Apr 20 nicklas 122   {
5916 22 Apr 20 nicklas 123     return numCreated;
5916 22 Apr 20 nicklas 124   }
5916 22 Apr 20 nicklas 125   
5916 22 Apr 20 nicklas 126   public int getNumExisting()
5916 22 Apr 20 nicklas 127   {
5916 22 Apr 20 nicklas 128     return numExisting;
5916 22 Apr 20 nicklas 129   }
5916 22 Apr 20 nicklas 130   
5910 17 Apr 20 nicklas 131   /**
5910 17 Apr 20 nicklas 132     Import data from the input stream.
5910 17 Apr 20 nicklas 133     @param in
5910 17 Apr 20 nicklas 134   */
5916 22 Apr 20 nicklas 135   public boolean doImport(DbControl dc, InputStream in, String workSheet, boolean validateOnly, String fileName, ProgressReporter progress)
5910 17 Apr 20 nicklas 136     throws IOException
5910 17 Apr 20 nicklas 137   {
5910 17 Apr 20 nicklas 138     FlatFileParser ffp = getFlatFileParser();
5910 17 Apr 20 nicklas 139     ffp.setExcelSheet(workSheet);
5910 17 Apr 20 nicklas 140     ffp.setInputStream(in, "UTF-8");
5910 17 Apr 20 nicklas 141     
5910 17 Apr 20 nicklas 142     XlsxToCsvUtil workbook = ffp.getCurrentExcelWorkbook();
5910 17 Apr 20 nicklas 143     if (workbook != null)
5910 17 Apr 20 nicklas 144     {
5910 17 Apr 20 nicklas 145       worksheets = workbook.getSheetNames();
5910 17 Apr 20 nicklas 146       parsedWorksheet = ffp.getExcelSheet();
5910 17 Apr 20 nicklas 147       fileName += "/" + parsedWorksheet;
5910 17 Apr 20 nicklas 148     }
5910 17 Apr 20 nicklas 149
5916 22 Apr 20 nicklas 150     progress.display(5, "Parsing file " + fileName);
5916 22 Apr 20 nicklas 151     
5910 17 Apr 20 nicklas 152     LineType headerLine = ffp.parseHeaders();
5910 17 Apr 20 nicklas 153     int lineNo = ffp.getParsedLines();
5910 17 Apr 20 nicklas 154     if (headerLine != LineType.DATA_HEADER)
5910 17 Apr 20 nicklas 155     {
5910 17 Apr 20 nicklas 156       addErrorMessage("File '" + fileName + "' line 1−" + lineNo + ": Could not find header line with 'BloodRccidNumber'");
5910 17 Apr 20 nicklas 157       return false; // Can't continue if no data is found in the file
5910 17 Apr 20 nicklas 158     }
5910 17 Apr 20 nicklas 159     
5910 17 Apr 20 nicklas 160     String fileAndLine = "File '" + fileName + "' line " + lineNo + ": ";
5910 17 Apr 20 nicklas 161
5911 21 Apr 20 nicklas 162     Formatter<Date> dateFormat = new DateFormatter("yyyy-MM-dd");
5973 24 Jun 20 nicklas 163     Formatter<Date> timeFormat = new DateFormatter("HH:mm");
5911 21 Apr 20 nicklas 164     
5911 21 Apr 20 nicklas 165     Mapper rccIdMapper = getRequiredMapper(ffp, "BloodRccidNumber", null, fileAndLine);
5911 21 Apr 20 nicklas 166     Mapper pnrMapper = getRequiredMapper(ffp, "PersonalNumber", null, fileAndLine);
5911 21 Apr 20 nicklas 167     Mapper caseMapper = getRequiredMapper(ffp, "Name", null, fileAndLine); // This is a Case name
5911 21 Apr 20 nicklas 168     Mapper bloodSampleMapper = getRequiredMapper(ffp, "BloodSample", null, fileAndLine);
5911 21 Apr 20 nicklas 169     Mapper samplingDateMapper = getRequiredMapper(ffp, "BloodSamplingDate", dateFormat, fileAndLine);
5911 21 Apr 20 nicklas 170     Mapper samplingTimeMapper = getRequiredMapper(ffp, "BloodSamplingTime", timeFormat, fileAndLine);
5911 21 Apr 20 nicklas 171     Mapper freezerDateMapper = getRequiredMapper(ffp, "BloodFreezerDate", dateFormat, fileAndLine);
5911 21 Apr 20 nicklas 172     Mapper freezerTimeMapper = getRequiredMapper(ffp, "BloodFreezerTime", timeFormat, fileAndLine);
5973 24 Jun 20 nicklas 173     Mapper pathNoteMapper = getRequiredMapper(ffp, "OtherPathNote", null, fileAndLine);
5911 21 Apr 20 nicklas 174     
5910 17 Apr 20 nicklas 175     if (hasError()) return false;
5910 17 Apr 20 nicklas 176
5911 21 Apr 20 nicklas 177     // Keep track of RCC-ID values and check for duplicates
5911 21 Apr 20 nicklas 178     uniqueItems = new HashMap<>();
5911 21 Apr 20 nicklas 179     allowedBloodSampleValues = new HashSet<>(Arrays.asList("FollowUp06", "FollowUp12", "FollowUp36"));
6801 11 Aug 22 nicklas 180     rccIdPattern = Pattern.compile("\\d{4}-\\d{8}");  // New variant is 4 digits + '-' + 8 digits (NNNN-NNNNNNNN)  
6801 11 Aug 22 nicklas 181     rccIdPatternOld = Pattern.compile("\\d{10}(B|C|D)");  // Old variant of RCC-id should be 10 digits followed by B, C or D
5911 21 Apr 20 nicklas 182     
5916 22 Apr 20 nicklas 183     List<BloodEntry> validEntries = new ArrayList<>();
5910 17 Apr 20 nicklas 184     while (ffp.hasMoreData())
5910 17 Apr 20 nicklas 185     {
5910 17 Apr 20 nicklas 186       FlatFileParser.Data data = ffp.nextData();
5910 17 Apr 20 nicklas 187       lineNo = ffp.getParsedLines();
5917 23 Apr 20 nicklas 188       fileAndLine = "Line " + lineNo + ": ";
5910 17 Apr 20 nicklas 189       numEntries++;
5911 21 Apr 20 nicklas 190       
5911 21 Apr 20 nicklas 191       BloodEntry b = new BloodEntry(fileAndLine);
5911 21 Apr 20 nicklas 192       b.lineNo = lineNo;
5911 21 Apr 20 nicklas 193       b.rccId = rccIdMapper.getString(data);
5911 21 Apr 20 nicklas 194       b.pnr = pnrMapper.getString(data);
5911 21 Apr 20 nicklas 195       b.caseName = caseMapper.getString(data);
5911 21 Apr 20 nicklas 196       b.bloodSample = bloodSampleMapper.getString(data);
5911 21 Apr 20 nicklas 197       b.samplingDate = samplingDateMapper.getDate(data);
5911 21 Apr 20 nicklas 198       b.samplingDateText = samplingDateMapper.getString(data);
5911 21 Apr 20 nicklas 199       b.samplingTime = samplingTimeMapper.getDate(data);
5911 21 Apr 20 nicklas 200       b.samplingTimeText = samplingTimeMapper.getString(data);
5917 23 Apr 20 nicklas 201       b.samplingDateTime = addDateAndTime(b.samplingDate, b.samplingTime);
5911 21 Apr 20 nicklas 202       
5911 21 Apr 20 nicklas 203       b.freezerDate = freezerDateMapper.getDate(data);
5911 21 Apr 20 nicklas 204       b.freezerDateText = freezerDateMapper.getString(data);
5911 21 Apr 20 nicklas 205       b.freezerTime = freezerTimeMapper.getDate(data);
5911 21 Apr 20 nicklas 206       b.freezerTimeText = freezerTimeMapper.getString(data);
5917 23 Apr 20 nicklas 207       b.freezerDateTime = addDateAndTime(b.freezerDate, b.freezerTime);
5917 23 Apr 20 nicklas 208
5911 21 Apr 20 nicklas 209       b.otherPathNote = pathNoteMapper.getString(data);
5911 21 Apr 20 nicklas 210       
5911 21 Apr 20 nicklas 211       boolean valid = checkBloodEntry(b);
5916 22 Apr 20 nicklas 212       if (valid) 
5916 22 Apr 20 nicklas 213       {
5916 22 Apr 20 nicklas 214         numValid++;
5916 22 Apr 20 nicklas 215         validEntries.add(b);
5916 22 Apr 20 nicklas 216       }
5916 22 Apr 20 nicklas 217     }
5916 22 Apr 20 nicklas 218     
5931 06 May 20 nicklas 219     if (numEntries == 0)
5931 06 May 20 nicklas 220     {
5931 06 May 20 nicklas 221       addErrorMessage("File '" + fileName + "': No data found after " + lineNo + " lines");
5931 06 May 20 nicklas 222     }
5931 06 May 20 nicklas 223     
5916 22 Apr 20 nicklas 224     if (hasError()) return false;
5916 22 Apr 20 nicklas 225     
5916 22 Apr 20 nicklas 226     if (validateOnly) return true;
5916 22 Apr 20 nicklas 227     
5916 22 Apr 20 nicklas 228     // Import step
5916 22 Apr 20 nicklas 229     int numProcessed = 0;
5916 22 Apr 20 nicklas 230     
5916 22 Apr 20 nicklas 231     ItemQuery<Sample> findBlood = Sample.getQuery();
5916 22 Apr 20 nicklas 232     findBlood.setIncludes(Reggie.INCLUDE_IN_CURRENT_PROJECT);
5916 22 Apr 20 nicklas 233     Subtype.BLOOD.addFilter(dc, findBlood);
5916 22 Apr 20 nicklas 234     findBlood.join(Annotations.innerJoin(Annotationtype.BLOOD_RCCIDNUMBER.get(dc), "rccId"));
5916 22 Apr 20 nicklas 235     findBlood.restrict(Restrictions.eq(Hql.alias("rccId"), Expressions.parameter("rccId", Type.STRING)));
5916 22 Apr 20 nicklas 236     
5917 23 Apr 20 nicklas 237     ItemQuery<BioSource> findPatient = BioSource.getQuery();
5917 23 Apr 20 nicklas 238     findPatient.setIncludes(Reggie.INCLUDE_IN_CURRENT_PROJECT);
5917 23 Apr 20 nicklas 239     Subtype.PATIENT.addFilter(dc, findPatient);
5917 23 Apr 20 nicklas 240     findPatient.join(Annotations.innerJoin(Annotationtype.PERSONAL_NUMBER.get(dc), "pnr"));
5917 23 Apr 20 nicklas 241     findPatient.restrict(Restrictions.eq(Hql.alias("pnr"), Expressions.parameter("pnr", Type.STRING)));
5917 23 Apr 20 nicklas 242     
5918 24 Apr 20 nicklas 243     // Query to find items with consent that are linked to a patient
5918 24 Apr 20 nicklas 244     ItemQuery<Sample> findConsentItems = Sample.getQuery();
6801 11 Aug 22 nicklas 245     findConsentItems.setIncludes(Reggie.INCLUDE_IN_CURRENT_PROJECT);
5918 24 Apr 20 nicklas 246     findConsentItems.join(Hql.innerJoin("parent", "pat"));
5918 24 Apr 20 nicklas 247     findConsentItems.join(Annotations.innerJoin(Annotationtype.CONSENT.get(dc), "cst")); // INNER JOIN is implicit restriction that a consent exists
5918 24 Apr 20 nicklas 248     findConsentItems.restrict(Restrictions.eq(Hql.alias("pat"), Hql.entityParameter("patient", Item.BIOSOURCE)));
5918 24 Apr 20 nicklas 249     
5917 23 Apr 20 nicklas 250     ItemSubtype bloodType = Subtype.BLOOD.get(dc);
5918 24 Apr 20 nicklas 251     String nextBlood_XId = null;
5916 22 Apr 20 nicklas 252     for (BloodEntry b : validEntries)
5916 22 Apr 20 nicklas 253     {
5916 22 Apr 20 nicklas 254       findBlood.setParameter("rccId", b.rccId, Type.STRING);
5916 22 Apr 20 nicklas 255       List<Sample> blood = findBlood.list(dc);
5916 22 Apr 20 nicklas 256       if (blood.size() == 0) 
5916 22 Apr 20 nicklas 257       {
5917 23 Apr 20 nicklas 258         findPatient.setParameter("pnr", b.pnr, Type.STRING);
5917 23 Apr 20 nicklas 259         List<BioSource> patient = findPatient.list(dc); // We should find exactly ONE
5917 23 Apr 20 nicklas 260         if (patient.size() != 1)
5917 23 Apr 20 nicklas 261         {
5917 23 Apr 20 nicklas 262           numValid--;
5917 23 Apr 20 nicklas 263           if (patient.size() == 0)
5917 23 Apr 20 nicklas 264           {
5917 23 Apr 20 nicklas 265             addErrorMessage(b.fileAndLine+"Could not find a patient with personal number: " + b.pnr);
5917 23 Apr 20 nicklas 266           }
5917 23 Apr 20 nicklas 267           else
5917 23 Apr 20 nicklas 268           {
5917 23 Apr 20 nicklas 269             addErrorMessage(b.fileAndLine+"Found " + patient.size() + " patients with personal number: " + b.pnr);
5917 23 Apr 20 nicklas 270           }
5917 23 Apr 20 nicklas 271         }
5917 23 Apr 20 nicklas 272         else
5917 23 Apr 20 nicklas 273         {
5917 23 Apr 20 nicklas 274           // Create a new follow-up Blood entry
5917 23 Apr 20 nicklas 275           BioSource pat = patient.get(0);
5917 23 Apr 20 nicklas 276           Sample fuBlood = Sample.getNew(dc);
5917 23 Apr 20 nicklas 277           fuBlood.setItemSubtype(bloodType);
5917 23 Apr 20 nicklas 278           fuBlood.setName(Blood.getNextBloodName(dc, b.caseName));
5918 24 Apr 20 nicklas 279           // The first new external id is generated from what is already in the database
5918 24 Apr 20 nicklas 280           // After that we simply use the last id to get a new one
5918 24 Apr 20 nicklas 281           nextBlood_XId = nextBlood_XId == null ? Blood.getNextExternalId(dc) : ReggieItem.getNextExternalId(dc, nextBlood_XId);
5918 24 Apr 20 nicklas 282           fuBlood.setExternalId(nextBlood_XId);
5917 23 Apr 20 nicklas 283           
5917 23 Apr 20 nicklas 284           BioMaterialEvent createEvent = fuBlood.getCreationEvent();
5917 23 Apr 20 nicklas 285           createEvent.setEventDate(b.samplingDate);
5917 23 Apr 20 nicklas 286           createEvent.addSource(pat);
5917 23 Apr 20 nicklas 287           
5917 23 Apr 20 nicklas 288           Annotationtype.BLOOD_RCCIDNUMBER.setAnnotationValue(dc, fuBlood, b.rccId);
5917 23 Apr 20 nicklas 289           Annotationtype.BLOOD_SAMPLE.setAnnotationValue(dc, fuBlood, b.bloodSample);
5917 23 Apr 20 nicklas 290           Annotationtype.BLOOD_SAMPLING_DATETIME.setAnnotationValue(dc, fuBlood, b.samplingDateTime);
5917 23 Apr 20 nicklas 291           Annotationtype.BLOOD_FREEZER_DATETIME.setAnnotationValue(dc, fuBlood, b.freezerDateTime);
5917 23 Apr 20 nicklas 292           Annotationtype.BLOOD_SERUM.setAnnotationValue(dc, fuBlood, "Yes");
5917 23 Apr 20 nicklas 293           Annotationtype.OTHER_PATH_NOTE.setAnnotationValue(dc, fuBlood, b.otherPathNote);
5917 23 Apr 20 nicklas 294           
5918 24 Apr 20 nicklas 295           findConsentItems.setEntityParameter("patient", pat);
5918 24 Apr 20 nicklas 296           List<Sample> items = findConsentItems.list(dc);
5918 24 Apr 20 nicklas 297           if (items.size() > 0)
5918 24 Apr 20 nicklas 298           {
5918 24 Apr 20 nicklas 299             Sample copyFrom = null;
5918 24 Apr 20 nicklas 300             for (Sample s : items)
5918 24 Apr 20 nicklas 301             {
5918 24 Apr 20 nicklas 302               if (s.getName().startsWith(b.caseName))
5918 24 Apr 20 nicklas 303               {
5918 24 Apr 20 nicklas 304                 copyFrom = s;
5918 24 Apr 20 nicklas 305                 break;
5918 24 Apr 20 nicklas 306               }
5918 24 Apr 20 nicklas 307             }
5918 24 Apr 20 nicklas 308             if (copyFrom == null) copyFrom = items.get(0);
5918 24 Apr 20 nicklas 309             Consent.copyConsentAnnotations(dc, copyFrom, fuBlood, false);
5918 24 Apr 20 nicklas 310           }
5917 23 Apr 20 nicklas 311           
5917 23 Apr 20 nicklas 312           dc.saveItem(fuBlood);
5917 23 Apr 20 nicklas 313           numCreated++;
5917 23 Apr 20 nicklas 314         }
5916 22 Apr 20 nicklas 315       }
5916 22 Apr 20 nicklas 316       else
5916 22 Apr 20 nicklas 317       {
5917 23 Apr 20 nicklas 318         if (blood.size() > 1)
5917 23 Apr 20 nicklas 319         {
5917 23 Apr 20 nicklas 320           addWarningMessage(b.fileAndLine + "Found " + blood.size() + " existing blood items with same RCC ID: " + b.rccId);
5917 23 Apr 20 nicklas 321         }
5917 23 Apr 20 nicklas 322         for (Sample s : blood)
5917 23 Apr 20 nicklas 323         {
5917 23 Apr 20 nicklas 324           dc.detachItem(s); // Don't need to keep this in the transaction
5917 23 Apr 20 nicklas 325         }
5916 22 Apr 20 nicklas 326         numExisting++;
5916 22 Apr 20 nicklas 327       }
5916 22 Apr 20 nicklas 328       
5916 22 Apr 20 nicklas 329       numProcessed++;
5911 21 Apr 20 nicklas 330
5916 22 Apr 20 nicklas 331       if (numProcessed % 100 == 0)
5916 22 Apr 20 nicklas 332       {
5918 24 Apr 20 nicklas 333         String msg = "Processing " + numProcessed + " of " + numEntries + " entries; ";
5918 24 Apr 20 nicklas 334         msg += numCreated + " new ";
5918 24 Apr 20 nicklas 335         if (numValid != numEntries) msg += "and " + (numEntries-numValid) + " invalid ";
5918 24 Apr 20 nicklas 336         msg += "so far...";
5918 24 Apr 20 nicklas 337         progress.display(10 + (80*numProcessed) / numEntries, msg);
5916 22 Apr 20 nicklas 338       }
5916 22 Apr 20 nicklas 339
5910 17 Apr 20 nicklas 340     }
5910 17 Apr 20 nicklas 341     
5910 17 Apr 20 nicklas 342     return !hasError();
5910 17 Apr 20 nicklas 343   }
5910 17 Apr 20 nicklas 344   
5910 17 Apr 20 nicklas 345
5910 17 Apr 20 nicklas 346   protected FlatFileParser getFlatFileParser()
5910 17 Apr 20 nicklas 347   {
5910 17 Apr 20 nicklas 348
5910 17 Apr 20 nicklas 349     FlatFileParser ffp = new FlatFileParser();
5910 17 Apr 20 nicklas 350     ffp.setDataHeaderRegexp(Pattern.compile(".*BloodRccidNumber.*"));
5910 17 Apr 20 nicklas 351     ffp.setDataSplitterRegexp(Pattern.compile("\\t"));
5931 06 May 20 nicklas 352     ffp.setIgnoreRegexp(Pattern.compile("#.*|\\s*"));
5910 17 Apr 20 nicklas 353     ffp.setUseNullIfEmpty(true);
5910 17 Apr 20 nicklas 354     ffp.setTrimWhiteSpace(true);
5910 17 Apr 20 nicklas 355     return ffp;
5910 17 Apr 20 nicklas 356   }
5910 17 Apr 20 nicklas 357
5910 17 Apr 20 nicklas 358   /**
5910 17 Apr 20 nicklas 359     Get a mapper for a required column. If the column doesn't exists
5910 17 Apr 20 nicklas 360     an error message is logged and null is returned.
5910 17 Apr 20 nicklas 361   */
5911 21 Apr 20 nicklas 362   private Mapper getRequiredMapper(FlatFileParser ffp, String col, Formatter<Date> dateFormat, String fileAndLine)
5910 17 Apr 20 nicklas 363   {
5910 17 Apr 20 nicklas 364     Mapper mapper = null;
5910 17 Apr 20 nicklas 365     if (ffp.getColumnHeaderIndex(col) == null)
5910 17 Apr 20 nicklas 366     {
5910 17 Apr 20 nicklas 367       addErrorMessage(fileAndLine+"Column '" + col + "' not found in column headers.");
5910 17 Apr 20 nicklas 368     }
5910 17 Apr 20 nicklas 369     else
5910 17 Apr 20 nicklas 370     {
5911 21 Apr 20 nicklas 371       mapper = ffp.getMapper("\\" + col + "\\", dateFormat, true);
5910 17 Apr 20 nicklas 372     }
5910 17 Apr 20 nicklas 373     return mapper;
5910 17 Apr 20 nicklas 374   }
5910 17 Apr 20 nicklas 375   
5910 17 Apr 20 nicklas 376   /**
5910 17 Apr 20 nicklas 377     Get a mapper for an optional column. If the column doesn't exists
5910 17 Apr 20 nicklas 378     a warning message is logged (unless 'fileAndLine' is null) 
5910 17 Apr 20 nicklas 379     and null is returned.
5910 17 Apr 20 nicklas 380   */
5910 17 Apr 20 nicklas 381   private Mapper getOptionalMapper(FlatFileParser ffp, String col, String fileAndLine)
5910 17 Apr 20 nicklas 382   {
5910 17 Apr 20 nicklas 383     Mapper mapper = null;
5910 17 Apr 20 nicklas 384     if (ffp.getColumnHeaderIndex(col) == null)
5910 17 Apr 20 nicklas 385     {
5910 17 Apr 20 nicklas 386       if (fileAndLine != null)
5910 17 Apr 20 nicklas 387       {
5910 17 Apr 20 nicklas 388         addWarningMessage(fileAndLine+"Column '" + col + "' not found in column headers.");
5910 17 Apr 20 nicklas 389       }
5910 17 Apr 20 nicklas 390     }
5910 17 Apr 20 nicklas 391     else
5910 17 Apr 20 nicklas 392     {
5910 17 Apr 20 nicklas 393       mapper = ffp.getMapper("\\" + col + "\\");
5910 17 Apr 20 nicklas 394     }
5910 17 Apr 20 nicklas 395     return mapper;
5910 17 Apr 20 nicklas 396   }
5911 21 Apr 20 nicklas 397   
5911 21 Apr 20 nicklas 398   public boolean checkBloodEntry(BloodEntry b)
5911 21 Apr 20 nicklas 399   {
5911 21 Apr 20 nicklas 400     if (b.rccId == null)
5911 21 Apr 20 nicklas 401     {
5911 21 Apr 20 nicklas 402       addErrorMessage(b.fileAndLine+"Missing 'BloodRccidNumber'");
5911 21 Apr 20 nicklas 403       return false;
5911 21 Apr 20 nicklas 404     }
5911 21 Apr 20 nicklas 405     else
5911 21 Apr 20 nicklas 406     {
5911 21 Apr 20 nicklas 407       Integer duplicateLine = uniqueItems.get(b.rccId);
5911 21 Apr 20 nicklas 408       if (duplicateLine != null)
5911 21 Apr 20 nicklas 409       {
5911 21 Apr 20 nicklas 410         addErrorMessage(b.fileAndLine + "Duplicate 'BloodRccidNumber': " + b.rccId + " (also found on line " + duplicateLine + ")");
5911 21 Apr 20 nicklas 411         return false;
5911 21 Apr 20 nicklas 412       }
5911 21 Apr 20 nicklas 413       uniqueItems.put(b.rccId, b.lineNo);
5911 21 Apr 20 nicklas 414     }
6801 11 Aug 22 nicklas 415     if (!rccIdPattern.matcher(b.rccId).matches() && !rccIdPatternOld.matcher(b.rccId).matches())
5911 21 Apr 20 nicklas 416     {
6801 11 Aug 22 nicklas 417       addErrorMessage(b.fileAndLine + "Invalid 'BloodRccidNumber': " + b.rccId + " (expected format is NNNN-NNNNNNNN where N is a digit, or 10 digits followed by B, C or D)");
5911 21 Apr 20 nicklas 418       return false;
5911 21 Apr 20 nicklas 419     }
5910 17 Apr 20 nicklas 420
5911 21 Apr 20 nicklas 421     if (b.pnr == null)
5911 21 Apr 20 nicklas 422     {
5911 21 Apr 20 nicklas 423       addErrorMessage(b.fileAndLine+"Missing 'PersonalNumber'");
5911 21 Apr 20 nicklas 424       return false;
5911 21 Apr 20 nicklas 425     }
5911 21 Apr 20 nicklas 426     if (b.caseName == null)
5911 21 Apr 20 nicklas 427     {
5911 21 Apr 20 nicklas 428       addErrorMessage(b.fileAndLine+"Missing 'Name'");
5911 21 Apr 20 nicklas 429       return false;
5911 21 Apr 20 nicklas 430     }
5911 21 Apr 20 nicklas 431     
5911 21 Apr 20 nicklas 432     if (b.bloodSample == null)
5911 21 Apr 20 nicklas 433     {
5911 21 Apr 20 nicklas 434       addErrorMessage(b.fileAndLine+"Missing 'BloodSample'");
5911 21 Apr 20 nicklas 435       return false;
5911 21 Apr 20 nicklas 436     }
5911 21 Apr 20 nicklas 437     else if (!allowedBloodSampleValues.contains(b.bloodSample))
5911 21 Apr 20 nicklas 438     {
5911 21 Apr 20 nicklas 439       addErrorMessage(b.fileAndLine+"Invalid 'BloodSample' value: " + b.bloodSample + " (expected one of '" + Values.getString(allowedBloodSampleValues, "', '", true) + "')");
5911 21 Apr 20 nicklas 440       return false;
5911 21 Apr 20 nicklas 441     }
5911 21 Apr 20 nicklas 442     
5911 21 Apr 20 nicklas 443     if (b.samplingDateText != null && b.samplingDate == null)
5911 21 Apr 20 nicklas 444     {
5911 21 Apr 20 nicklas 445       // The text could not be converted to a date 
5911 21 Apr 20 nicklas 446       addErrorMessage(b.fileAndLine+"Invalid 'BloodSamplingDate': " + b.samplingDateText + " (expected format is YYYY-MM-DD)");
5911 21 Apr 20 nicklas 447       return false;
5911 21 Apr 20 nicklas 448     }
5911 21 Apr 20 nicklas 449     if (b.samplingTimeText != null && b.samplingTime == null)
5911 21 Apr 20 nicklas 450     {
5911 21 Apr 20 nicklas 451       addErrorMessage(b.fileAndLine+"Invalid 'BloodSamplingTime': " + b.samplingTimeText + " (expected format is HH:MM)");
5911 21 Apr 20 nicklas 452       return false;
5911 21 Apr 20 nicklas 453     }
5911 21 Apr 20 nicklas 454
5911 21 Apr 20 nicklas 455     if (b.freezerDateText != null && b.freezerDate == null)
5911 21 Apr 20 nicklas 456     {
5911 21 Apr 20 nicklas 457       addErrorMessage(b.fileAndLine+"Invalid 'BloodFreezerDate': " + b.freezerDateText + " (expected format is YYYY-MM-DD)");
5911 21 Apr 20 nicklas 458       return false;
5911 21 Apr 20 nicklas 459     }
5911 21 Apr 20 nicklas 460     if (b.freezerTimeText != null && b.freezerTime == null)
5911 21 Apr 20 nicklas 461     {
5911 21 Apr 20 nicklas 462       addErrorMessage(b.fileAndLine+"Invalid 'BloodFreezerTime': " + b.freezerTimeText + " (expected format is HH:MM)");
5911 21 Apr 20 nicklas 463       return false;
5911 21 Apr 20 nicklas 464     }
5911 21 Apr 20 nicklas 465     
5911 21 Apr 20 nicklas 466     return true;
5911 21 Apr 20 nicklas 467   }
5911 21 Apr 20 nicklas 468
5917 23 Apr 20 nicklas 469   // Add date and time together using only the date and time for each of the value
5917 23 Apr 20 nicklas 470   // If the date is null, null is returned, if time is null, the date is returned with time set to 00:00
5917 23 Apr 20 nicklas 471   private Date addDateAndTime(Date date, Date time)
5917 23 Apr 20 nicklas 472   {
5917 23 Apr 20 nicklas 473     if (date == null) return null;
5917 23 Apr 20 nicklas 474
5917 23 Apr 20 nicklas 475     Calendar dateTime = Calendar.getInstance();
5917 23 Apr 20 nicklas 476     dateTime.setTime(date);
5917 23 Apr 20 nicklas 477     if (time != null)
5917 23 Apr 20 nicklas 478     {
5917 23 Apr 20 nicklas 479       Calendar tc = Calendar.getInstance();
5917 23 Apr 20 nicklas 480       tc.setTime(time);
5973 24 Jun 20 nicklas 481       dateTime.set(Calendar.HOUR_OF_DAY, tc.get(Calendar.HOUR_OF_DAY));
5917 23 Apr 20 nicklas 482       dateTime.set(Calendar.MINUTE, tc.get(Calendar.MINUTE));
5917 23 Apr 20 nicklas 483     }
5917 23 Apr 20 nicklas 484     else
5917 23 Apr 20 nicklas 485     {
5973 24 Jun 20 nicklas 486       dateTime.set(Calendar.HOUR_OF_DAY, 0);
5917 23 Apr 20 nicklas 487       dateTime.set(Calendar.MINUTE, 0);
5917 23 Apr 20 nicklas 488     }
5917 23 Apr 20 nicklas 489     dateTime.set(Calendar.SECOND, 0);
5917 23 Apr 20 nicklas 490     dateTime.set(Calendar.MILLISECOND, 0);
5917 23 Apr 20 nicklas 491     return dateTime.getTime();
5917 23 Apr 20 nicklas 492   }
5917 23 Apr 20 nicklas 493   
5911 21 Apr 20 nicklas 494   public static class BloodEntry
5911 21 Apr 20 nicklas 495   {
5911 21 Apr 20 nicklas 496     final String fileAndLine;
5911 21 Apr 20 nicklas 497     int lineNo;
5911 21 Apr 20 nicklas 498     String pnr;
5911 21 Apr 20 nicklas 499     String rccId;
5911 21 Apr 20 nicklas 500     String caseName;
5911 21 Apr 20 nicklas 501     String bloodSample;
5911 21 Apr 20 nicklas 502     String samplingDateText;
5911 21 Apr 20 nicklas 503     String samplingTimeText;
5911 21 Apr 20 nicklas 504     Date samplingDate;
5911 21 Apr 20 nicklas 505     Date samplingTime;
5917 23 Apr 20 nicklas 506     Date samplingDateTime;
5911 21 Apr 20 nicklas 507     String freezerDateText;
5911 21 Apr 20 nicklas 508     String freezerTimeText;
5917 23 Apr 20 nicklas 509     Date freezerDateTime;
5911 21 Apr 20 nicklas 510     Date freezerDate;
5911 21 Apr 20 nicklas 511     Date freezerTime;
5911 21 Apr 20 nicklas 512     String otherPathNote;
5911 21 Apr 20 nicklas 513     
5911 21 Apr 20 nicklas 514     public BloodEntry(String fileAndLine)
5911 21 Apr 20 nicklas 515     {
5911 21 Apr 20 nicklas 516       this.fileAndLine = fileAndLine;
5911 21 Apr 20 nicklas 517     }
5911 21 Apr 20 nicklas 518   }
5910 17 Apr 20 nicklas 519   
5910 17 Apr 20 nicklas 520 }