6207 |
12 Apr 21 |
nicklas |
1 |
package net.sf.basedb.reggie.plugins.cmd; |
6207 |
12 Apr 21 |
nicklas |
2 |
|
6207 |
12 Apr 21 |
nicklas |
3 |
import java.util.Date; |
6510 |
03 Dec 21 |
nicklas |
4 |
import java.util.List; |
6510 |
03 Dec 21 |
nicklas |
5 |
|
6510 |
03 Dec 21 |
nicklas |
6 |
import net.sf.basedb.core.DbControl; |
6510 |
03 Dec 21 |
nicklas |
7 |
import net.sf.basedb.core.Extract; |
6510 |
03 Dec 21 |
nicklas |
8 |
import net.sf.basedb.core.ItemQuery; |
6207 |
12 Apr 21 |
nicklas |
9 |
import net.sf.basedb.core.Protocol; |
6510 |
03 Dec 21 |
nicklas |
10 |
import net.sf.basedb.core.Type; |
6207 |
12 Apr 21 |
nicklas |
11 |
import net.sf.basedb.core.data.PlateCoordinate; |
6510 |
03 Dec 21 |
nicklas |
12 |
import net.sf.basedb.core.query.Annotations; |
6510 |
03 Dec 21 |
nicklas |
13 |
import net.sf.basedb.core.query.Expressions; |
6510 |
03 Dec 21 |
nicklas |
14 |
import net.sf.basedb.core.query.Hql; |
6510 |
03 Dec 21 |
nicklas |
15 |
import net.sf.basedb.core.query.Restrictions; |
6510 |
03 Dec 21 |
nicklas |
16 |
import net.sf.basedb.reggie.Reggie; |
6510 |
03 Dec 21 |
nicklas |
17 |
import net.sf.basedb.reggie.dao.Annotationtype; |
6510 |
03 Dec 21 |
nicklas |
18 |
import net.sf.basedb.reggie.dao.Subtype; |
6510 |
03 Dec 21 |
nicklas |
19 |
import net.sf.basedb.util.Coordinate; |
6714 |
29 Apr 22 |
nicklas |
20 |
import net.sf.basedb.util.Values; |
6207 |
12 Apr 21 |
nicklas |
21 |
|
6207 |
12 Apr 21 |
nicklas |
22 |
/** |
6207 |
12 Apr 21 |
nicklas |
Holds all information about a RNA. Validation will |
6207 |
12 Apr 21 |
nicklas |
be done at construction and errors are reported to |
6207 |
12 Apr 21 |
nicklas |
the JsonSection. Check the 'valid' flag before using |
6207 |
12 Apr 21 |
nicklas |
the information. |
6207 |
12 Apr 21 |
nicklas |
27 |
|
6207 |
12 Apr 21 |
nicklas |
@since 4.32 |
6207 |
12 Apr 21 |
nicklas |
29 |
*/ |
6207 |
12 Apr 21 |
nicklas |
30 |
public class RnaInfo |
6207 |
12 Apr 21 |
nicklas |
31 |
{ |
7232 |
02 Jun 23 |
nicklas |
32 |
public Extract mergeWith; |
6711 |
27 Apr 22 |
nicklas |
33 |
public String tubeLabel; |
6207 |
12 Apr 21 |
nicklas |
34 |
public Date qiacubeDate; |
6207 |
12 Apr 21 |
nicklas |
35 |
public Integer qiacubePos; |
6510 |
03 Dec 21 |
nicklas |
36 |
public String qiacubeRunId; |
6207 |
12 Apr 21 |
nicklas |
37 |
public Integer qiacubeRunNumber; |
6207 |
12 Apr 21 |
nicklas |
38 |
public String operator; |
6510 |
03 Dec 21 |
nicklas |
39 |
public Float rin; |
6207 |
12 Apr 21 |
nicklas |
40 |
public Float qubitConc; |
6714 |
29 Apr 22 |
nicklas |
41 |
public Float originalQuantity_ng; |
6714 |
29 Apr 22 |
nicklas |
42 |
public Float remainingQuantity_ng; |
6714 |
29 Apr 22 |
nicklas |
43 |
public Float usedVolumeFromLysate_ul; |
6918 |
01 Dec 22 |
nicklas |
44 |
public String storageLocation; |
6207 |
12 Apr 21 |
nicklas |
45 |
|
6207 |
12 Apr 21 |
nicklas |
46 |
public Protocol protocol; |
6207 |
12 Apr 21 |
nicklas |
47 |
public boolean valid; |
6207 |
12 Apr 21 |
nicklas |
48 |
|
6893 |
25 Nov 22 |
nicklas |
49 |
public RnaInfo(JsonSection section, SpecimenInfo specimen, LysateInfo lysate, MainInfo main, ImportContext ctx) |
6207 |
12 Apr 21 |
nicklas |
50 |
{ |
6207 |
12 Apr 21 |
nicklas |
51 |
if (section != null) |
6207 |
12 Apr 21 |
nicklas |
52 |
{ |
7232 |
02 Jun 23 |
nicklas |
53 |
if (lysate != null && lysate.mergeWith != null) |
7232 |
02 Jun 23 |
nicklas |
54 |
{ |
7232 |
02 Jun 23 |
nicklas |
55 |
mergeWith = findRNAForMerge(lysate, section); |
7232 |
02 Jun 23 |
nicklas |
56 |
} |
7232 |
02 Jun 23 |
nicklas |
57 |
|
6732 |
05 May 22 |
nicklas |
58 |
tubeLabel = section.getOptionalEntry("Specimen-ID", NullValidator.warnIfNull(PatternValidator.SPECIMEN_ID.withPrefixSuffix(specimen != null?specimen.tubeLabel:null, "-RNA"))); |
6893 |
25 Nov 22 |
nicklas |
59 |
qiacubeDate = section.getRequiredEntry("Qiacube date", DateValidator.YYYY_MM_DD.warnIfFutureOrOlder(lysate!=null?lysate.partitionDate:null, main.refDate)); |
6207 |
12 Apr 21 |
nicklas |
60 |
PlateCoordinate tmp = section.getRequiredEntry("Qiacube position", PlateWellValidator.QIACUBE); |
6207 |
12 Apr 21 |
nicklas |
61 |
if (tmp != null) qiacubePos = 6*tmp.getColumn()+tmp.getRow()+1; |
6212 |
14 Apr 21 |
nicklas |
62 |
operator = section.getOptionalEntry("Qiacube operator", null); |
6207 |
12 Apr 21 |
nicklas |
63 |
|
6510 |
03 Dec 21 |
nicklas |
64 |
qiacubeRunId = section.getRequiredEntry("Qiacube run number", PatternValidator.CMD_ID); |
6510 |
03 Dec 21 |
nicklas |
65 |
if (qiacubeRunId != null && qiacubePos != null && ctx != null) |
6510 |
03 Dec 21 |
nicklas |
66 |
{ |
6510 |
03 Dec 21 |
nicklas |
67 |
JsonSection duplicate = ctx.add("Qiacube:"+qiacubeRunId+":"+qiacubePos, section); |
6510 |
03 Dec 21 |
nicklas |
68 |
if (duplicate != null) |
6510 |
03 Dec 21 |
nicklas |
69 |
{ |
6510 |
03 Dec 21 |
nicklas |
70 |
String coordinate = (tmp.getRow()+1)+":"+Coordinate.numericToAlpha(tmp.getColumn()+1); |
6510 |
03 Dec 21 |
nicklas |
71 |
String msg = "Qiacube position ["+coordinate+"] duplicated in file: "; |
6510 |
03 Dec 21 |
nicklas |
72 |
section.addErrorMessage(msg+duplicate.getFile().getName()); |
6510 |
03 Dec 21 |
nicklas |
73 |
duplicate.addErrorMessage(msg+section.getFile().getName()); |
6510 |
03 Dec 21 |
nicklas |
74 |
} |
6510 |
03 Dec 21 |
nicklas |
75 |
} |
6510 |
03 Dec 21 |
nicklas |
76 |
if (qiacubeDate != null && qiacubePos != null && qiacubeRunId != null) |
6510 |
03 Dec 21 |
nicklas |
77 |
{ |
6510 |
03 Dec 21 |
nicklas |
78 |
qiacubeRunNumber = findQiacubeRunNumber(qiacubeDate, qiacubeRunId, qiacubePos, section); |
6510 |
03 Dec 21 |
nicklas |
79 |
} |
6510 |
03 Dec 21 |
nicklas |
80 |
|
6344 |
29 Jun 21 |
nicklas |
81 |
qubitConc = section.getRequiredEntry("Concentration (ng/ul)", FloatValidator.POSITIVE); |
6510 |
03 Dec 21 |
nicklas |
82 |
rin = section.getRequiredEntry("RIN", FloatValidator.RIN); |
6221 |
23 Apr 21 |
nicklas |
83 |
protocol = section.getRequiredEntry("Protocol", ProtocolValidator.EXTRACTION_PROTOCOL); |
6714 |
29 Apr 22 |
nicklas |
84 |
originalQuantity_ng = section.getOptionalEntry("Original quantity (ng)", NullValidator.warnIfNull(FloatValidator.POSITIVE)); |
6714 |
29 Apr 22 |
nicklas |
85 |
remainingQuantity_ng = section.getOptionalEntry("Remaining quantity (ng)", NullValidator.warnIfNull(FloatValidator.POSITIVE)); |
6946 |
06 Dec 22 |
nicklas |
86 |
if (originalQuantity_ng != null && remainingQuantity_ng != null) |
6714 |
29 Apr 22 |
nicklas |
87 |
{ |
6946 |
06 Dec 22 |
nicklas |
88 |
float usedQuantity_ng = originalQuantity_ng - remainingQuantity_ng; |
6946 |
06 Dec 22 |
nicklas |
89 |
if (usedQuantity_ng < 100) |
6946 |
06 Dec 22 |
nicklas |
90 |
{ |
6946 |
06 Dec 22 |
nicklas |
// The used quantity is maybe a volume in µl. Test this with help of the qubitConc |
6946 |
06 Dec 22 |
nicklas |
92 |
float actualUsedQuantity_ng = usedQuantity_ng * qubitConc; |
6946 |
06 Dec 22 |
nicklas |
93 |
if (actualUsedQuantity_ng > usedQuantity_ng && actualUsedQuantity_ng <= originalQuantity_ng) |
6946 |
06 Dec 22 |
nicklas |
94 |
{ |
6946 |
06 Dec 22 |
nicklas |
// Adjust |
6946 |
06 Dec 22 |
nicklas |
96 |
remainingQuantity_ng = originalQuantity_ng-actualUsedQuantity_ng; |
6946 |
06 Dec 22 |
nicklas |
97 |
section.addWarningMessage("Adjusting RNA.Used quantity from " + Values.formatNumber(usedQuantity_ng, 1, "ng") |
6946 |
06 Dec 22 |
nicklas |
98 |
+ " to " + Values.formatNumber(actualUsedQuantity_ng, 1, "ng")); |
6946 |
06 Dec 22 |
nicklas |
99 |
} |
6946 |
06 Dec 22 |
nicklas |
100 |
else |
6946 |
06 Dec 22 |
nicklas |
101 |
{ |
6946 |
06 Dec 22 |
nicklas |
102 |
section.addWarningMessage("RNA.Used quantity < 100ng: "+Values.formatNumber(usedQuantity_ng, 1, "ng")); |
6946 |
06 Dec 22 |
nicklas |
103 |
} |
6946 |
06 Dec 22 |
nicklas |
104 |
} |
6714 |
29 Apr 22 |
nicklas |
105 |
} |
6714 |
29 Apr 22 |
nicklas |
106 |
if (lysate != null) |
6714 |
29 Apr 22 |
nicklas |
107 |
{ |
6714 |
29 Apr 22 |
nicklas |
108 |
usedVolumeFromLysate_ul = lysate.usedVolumePerChildItem_ul; |
6714 |
29 Apr 22 |
nicklas |
109 |
} |
6510 |
03 Dec 21 |
nicklas |
110 |
|
6918 |
01 Dec 22 |
nicklas |
// TODO -- maybe this will change in the future |
6918 |
01 Dec 22 |
nicklas |
112 |
storageLocation = section.getOptionalEntry("Storage location", NullValidator.allowNull(String.class)); |
6918 |
01 Dec 22 |
nicklas |
113 |
|
6207 |
12 Apr 21 |
nicklas |
114 |
} |
6207 |
12 Apr 21 |
nicklas |
115 |
valid = section != null && !section.hasError(); |
6207 |
12 Apr 21 |
nicklas |
116 |
} |
6207 |
12 Apr 21 |
nicklas |
117 |
|
6510 |
03 Dec 21 |
nicklas |
118 |
/** |
6510 |
03 Dec 21 |
nicklas |
Find the Qiacube run number. We will look for existing RNA items on the given |
6510 |
03 Dec 21 |
nicklas |
date. If we find a match with the same QiacubeRunId we use that same run number. |
6510 |
03 Dec 21 |
nicklas |
But! If the match happens to be on the same position and error is triggered. |
6510 |
03 Dec 21 |
nicklas |
If we find RNA on the same date with no or different QiacubeRunId we use |
6510 |
03 Dec 21 |
nicklas |
the max existing run number +1. If not matched RNA is found, the run number is 1. |
6510 |
03 Dec 21 |
nicklas |
124 |
*/ |
6510 |
03 Dec 21 |
nicklas |
125 |
private Integer findQiacubeRunNumber(Date qiacubeDate, String qiacubeRunId, Integer qiacubePos, JsonSection section) |
6510 |
03 Dec 21 |
nicklas |
126 |
{ |
6510 |
03 Dec 21 |
nicklas |
127 |
ItemQuery<Extract> query = Extract.getQuery(); |
6510 |
03 Dec 21 |
nicklas |
128 |
DbControl dc = section.getFile().dc(); |
6510 |
03 Dec 21 |
nicklas |
129 |
query.setIncludes(Reggie.INCLUDE_IN_CURRENT_PROJECT); |
6510 |
03 Dec 21 |
nicklas |
130 |
Subtype.RNA.addFilter(dc, query); |
6510 |
03 Dec 21 |
nicklas |
131 |
query.join(Annotations.innerJoin(null, Annotationtype.QIACUBE_DATE.load(dc), "qcdate")); |
6510 |
03 Dec 21 |
nicklas |
132 |
query.restrict(Restrictions.eq(Hql.alias("qcdate"), Expressions.parameter("qcdate", qiacubeDate, Type.DATE))); |
6510 |
03 Dec 21 |
nicklas |
133 |
|
6510 |
03 Dec 21 |
nicklas |
134 |
List<Extract> list = query.list(dc); |
6510 |
03 Dec 21 |
nicklas |
135 |
Integer runNumberSameId = null; |
6510 |
03 Dec 21 |
nicklas |
136 |
Integer runNumberSameDate = null; |
6510 |
03 Dec 21 |
nicklas |
137 |
for (Extract rna : list) |
6510 |
03 Dec 21 |
nicklas |
138 |
{ |
6510 |
03 Dec 21 |
nicklas |
139 |
String runId = (String)Annotationtype.QIACUBE_RUN_ID.getAnnotationValue(dc, rna); |
6510 |
03 Dec 21 |
nicklas |
140 |
Integer pos = (Integer)Annotationtype.QIACUBE_POSITION.getAnnotationValue(dc, rna); |
6510 |
03 Dec 21 |
nicklas |
141 |
Integer runNumber = (Integer)Annotationtype.QIACUBE_RUN_NO.getAnnotationValue(dc, rna); |
6510 |
03 Dec 21 |
nicklas |
142 |
if (pos == null || runNumber == null) continue; // Should not happen if everything is correctly registered! |
6510 |
03 Dec 21 |
nicklas |
143 |
|
6510 |
03 Dec 21 |
nicklas |
144 |
if (qiacubeRunId.equals(runId)) |
6510 |
03 Dec 21 |
nicklas |
145 |
{ |
6510 |
03 Dec 21 |
nicklas |
146 |
runNumberSameId = runNumber; |
6510 |
03 Dec 21 |
nicklas |
147 |
if (qiacubePos.equals(pos)) |
6510 |
03 Dec 21 |
nicklas |
148 |
{ |
6510 |
03 Dec 21 |
nicklas |
149 |
section.addErrorMessage("Found existing RNA on Qiacube position "+pos+": "+rna.getName()); |
6510 |
03 Dec 21 |
nicklas |
150 |
} |
6510 |
03 Dec 21 |
nicklas |
151 |
} |
6510 |
03 Dec 21 |
nicklas |
152 |
else |
6510 |
03 Dec 21 |
nicklas |
153 |
{ |
6510 |
03 Dec 21 |
nicklas |
154 |
if (runNumberSameDate == null || runNumber > runNumberSameDate) |
6510 |
03 Dec 21 |
nicklas |
155 |
{ |
6510 |
03 Dec 21 |
nicklas |
156 |
runNumberSameDate = runNumber; |
6510 |
03 Dec 21 |
nicklas |
157 |
} |
6510 |
03 Dec 21 |
nicklas |
158 |
} |
6510 |
03 Dec 21 |
nicklas |
159 |
} |
6510 |
03 Dec 21 |
nicklas |
160 |
return runNumberSameId != null ? runNumberSameId : (runNumberSameDate != null ? runNumberSameDate+1 : 1); |
6510 |
03 Dec 21 |
nicklas |
161 |
} |
7232 |
02 Jun 23 |
nicklas |
162 |
|
7232 |
02 Jun 23 |
nicklas |
163 |
/** |
7232 |
02 Jun 23 |
nicklas |
If there is a Lysate to be merged, try to find a single child RNA |
7232 |
02 Jun 23 |
nicklas |
item that should also be merged. |
7232 |
02 Jun 23 |
nicklas |
166 |
*/ |
7232 |
02 Jun 23 |
nicklas |
167 |
private Extract findRNAForMerge(LysateInfo lysate, JsonSection section) |
7232 |
02 Jun 23 |
nicklas |
168 |
{ |
7232 |
02 Jun 23 |
nicklas |
169 |
DbControl dc = section.getFile().dc(); |
7232 |
02 Jun 23 |
nicklas |
170 |
ItemQuery<Extract> query = lysate.mergeWith.getChildExtracts(); |
7232 |
02 Jun 23 |
nicklas |
171 |
query.setIncludes(Reggie.INCLUDE_IN_CURRENT_PROJECT); |
7232 |
02 Jun 23 |
nicklas |
172 |
query.restrict(Subtype.RNA.restriction(dc, null)); |
7232 |
02 Jun 23 |
nicklas |
173 |
|
7232 |
02 Jun 23 |
nicklas |
174 |
Extract merge = null; |
7232 |
02 Jun 23 |
nicklas |
175 |
List<Extract> extracts = query.list(dc); |
7232 |
02 Jun 23 |
nicklas |
176 |
if (extracts.size() == 1) |
7232 |
02 Jun 23 |
nicklas |
177 |
{ |
7232 |
02 Jun 23 |
nicklas |
178 |
merge = extracts.get(0); |
7232 |
02 Jun 23 |
nicklas |
179 |
} |
7232 |
02 Jun 23 |
nicklas |
180 |
else if (extracts.size() == 0) |
7232 |
02 Jun 23 |
nicklas |
181 |
{ |
7232 |
02 Jun 23 |
nicklas |
// TODO -- we could allow this without an error and create a new child RNA |
7232 |
02 Jun 23 |
nicklas |
// but there is no current use case for this scenario |
7232 |
02 Jun 23 |
nicklas |
184 |
section.addErrorMessage("Found a Specimen for merge but it has no child RNA: "+lysate.mergeWith.getName()); |
7232 |
02 Jun 23 |
nicklas |
185 |
} |
7232 |
02 Jun 23 |
nicklas |
186 |
else |
7232 |
02 Jun 23 |
nicklas |
187 |
{ |
7232 |
02 Jun 23 |
nicklas |
188 |
section.addErrorMessage("Found a Specimen for merge but there are "+extracts.size()+" child RNA: "+lysate.mergeWith.getName()); |
7232 |
02 Jun 23 |
nicklas |
189 |
} |
7232 |
02 Jun 23 |
nicklas |
190 |
return merge; |
7232 |
02 Jun 23 |
nicklas |
191 |
} |
7232 |
02 Jun 23 |
nicklas |
192 |
|
6207 |
12 Apr 21 |
nicklas |
193 |
} |