1 package fr.ifremer.tutti.service.bigfin;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 import com.google.common.base.Charsets;
28 import com.google.common.base.Function;
29 import com.google.common.base.Preconditions;
30 import com.google.common.collect.ArrayListMultimap;
31 import com.google.common.collect.Collections2;
32 import com.google.common.collect.ListMultimap;
33 import com.google.common.collect.Maps;
34 import com.google.common.collect.Multimap;
35 import com.google.common.collect.Multimaps;
36 import com.google.common.io.Files;
37 import fr.ifremer.adagio.core.dao.referential.ObjectTypeCode;
38 import fr.ifremer.adagio.core.dao.referential.pmfm.PmfmId;
39 import fr.ifremer.tutti.persistence.entities.data.Attachment;
40 import fr.ifremer.tutti.persistence.entities.data.Attachments;
41 import fr.ifremer.tutti.persistence.entities.data.BatchContainer;
42 import fr.ifremer.tutti.persistence.entities.data.CatchBatch;
43 import fr.ifremer.tutti.persistence.entities.data.FishingOperation;
44 import fr.ifremer.tutti.persistence.entities.data.SampleCategoryModel;
45 import fr.ifremer.tutti.persistence.entities.data.SpeciesBatch;
46 import fr.ifremer.tutti.persistence.entities.data.SpeciesBatchFrequency;
47 import fr.ifremer.tutti.persistence.entities.data.SpeciesBatchFrequencys;
48 import fr.ifremer.tutti.persistence.entities.data.SpeciesBatchs;
49 import fr.ifremer.tutti.persistence.entities.protocol.SpeciesProtocol;
50 import fr.ifremer.tutti.persistence.entities.protocol.TuttiProtocol;
51 import fr.ifremer.tutti.persistence.entities.referential.Caracteristic;
52 import fr.ifremer.tutti.persistence.entities.referential.CaracteristicQualitativeValue;
53 import fr.ifremer.tutti.persistence.entities.referential.Species;
54 import fr.ifremer.tutti.persistence.entities.referential.Speciess;
55 import fr.ifremer.tutti.service.AbstractTuttiService;
56 import fr.ifremer.tutti.service.PersistenceService;
57 import fr.ifremer.tutti.service.TuttiServiceContext;
58 import fr.ifremer.tutti.service.bigfin.csv.BigfinDataRow;
59 import fr.ifremer.tutti.service.bigfin.csv.BigfinDataRowModel;
60 import fr.ifremer.tutti.service.bigfin.signs.Sex;
61 import fr.ifremer.tutti.service.bigfin.signs.Sign;
62 import fr.ifremer.tutti.service.bigfin.signs.Size;
63 import fr.ifremer.tutti.service.bigfin.signs.VracHorsVrac;
64 import fr.ifremer.tutti.type.WeightUnit;
65 import java.io.File;
66 import java.io.IOException;
67 import java.io.Reader;
68 import java.io.Serializable;
69 import java.text.DateFormat;
70 import java.util.ArrayList;
71 import java.util.Collection;
72 import java.util.HashMap;
73 import java.util.List;
74 import java.util.Map;
75 import java.util.Set;
76 import org.apache.commons.collections4.CollectionUtils;
77 import org.apache.commons.collections4.IterableUtils;
78 import org.apache.commons.lang3.StringUtils;
79 import org.apache.commons.logging.Log;
80 import org.apache.commons.logging.LogFactory;
81 import org.nuiton.csv.Import;
82 import org.nuiton.csv.ImportRuntimeException;
83 import org.nuiton.jaxx.application.ApplicationBusinessException;
84
85 import static org.nuiton.i18n.I18n.t;
86
87
88
89
90
91 public class BigfinImportService extends AbstractTuttiService {
92
93 private static final Log log = LogFactory.getLog(BigfinImportService.class);
94
95 protected PersistenceService persistenceService;
96
97 protected Caracteristic sizeCaracteristic;
98
99 protected Caracteristic sexCaracteristic;
100
101 protected Map<Sign, CaracteristicQualitativeValue> signsToCaracteristicValue;
102
103 @Override
104 public void setServiceContext(TuttiServiceContext context) {
105
106 super.setServiceContext(context);
107 persistenceService = getService(PersistenceService.class);
108
109 signsToCaracteristicValue = new HashMap<>();
110
111
112 sexCaracteristic = persistenceService.getSexCaracteristic();
113 Sex.NONE.registerSign(sexCaracteristic, signsToCaracteristicValue);
114 Sex.UNKNOWN.registerSign(sexCaracteristic, signsToCaracteristicValue);
115 Sex.MALE.registerSign(sexCaracteristic, signsToCaracteristicValue);
116 Sex.FEMALE.registerSign(sexCaracteristic, signsToCaracteristicValue);
117
118
119 sizeCaracteristic = persistenceService.getSizeCategoryCaracteristic();
120 Size.NOT_SIZED.registerSign(sizeCaracteristic, signsToCaracteristicValue);
121 Size.SMALL.registerSign(sizeCaracteristic, signsToCaracteristicValue);
122 Size.BIG.registerSign(sizeCaracteristic, signsToCaracteristicValue);
123
124
125 Caracteristic vracHorsVracCaracteristic = persistenceService.getSortedUnsortedCaracteristic();
126 VracHorsVrac.VRAC.registerSign(vracHorsVracCaracteristic, signsToCaracteristicValue);
127 VracHorsVrac.HORS_VRAC.registerSign(vracHorsVracCaracteristic, signsToCaracteristicValue);
128
129 }
130
131 private BigfinImportContext prepareImportContext(File importFile, FishingOperation operation, CatchBatch catchBatch) {
132
133 TuttiProtocol protocol = persistenceService.getProtocol();
134
135 if (protocol == null) {
136 throw new ApplicationBusinessException(t("tutti.service.bigfinimport.error.no.protocol"));
137 }
138
139 List<Species> allReferentSpecies = persistenceService.getAllReferentSpecies();
140 Map<String, Species> speciesBySurveyCode = Maps.newTreeMap();
141 for (Species species : allReferentSpecies) {
142 String surveyCode = species.getSurveyCode();
143 if (StringUtils.isNotBlank(surveyCode)) {
144 speciesBySurveyCode.put(surveyCode, species);
145
146 }
147 if (species.getRefTaxCode() != null) {
148 speciesBySurveyCode.put(species.getRefTaxCode(), species);
149 }
150 }
151
152 Map<String, SpeciesProtocol> speciesProtocolBySurveyCode = Maps.newTreeMap();
153 for (SpeciesProtocol speciesProtocol : protocol.getSpecies()) {
154 if (StringUtils.isNotBlank(speciesProtocol.getSpeciesSurveyCode())) {
155 speciesProtocolBySurveyCode.put(speciesProtocol.getSpeciesSurveyCode(), speciesProtocol);
156
157 } else {
158 speciesProtocolBySurveyCode.put(speciesProtocol.getSpeciesReferenceTaxonId().toString(), speciesProtocol);
159 }
160 }
161
162 BatchContainer<SpeciesBatch> rootSpeciesBatch = persistenceService.getRootSpeciesBatch(operation.getIdAsInt(), false);
163
164
165 if (!rootSpeciesBatch.isEmptyChildren()) {
166
167 Map<String, Species> speciesByReferenceTaxonId = Speciess.splitReferenceSpeciesByReferenceTaxonId(allReferentSpecies);
168
169 for (SpeciesBatch speciesBatch : rootSpeciesBatch.getChildren()) {
170 Species species = speciesBatch.getSpecies();
171 Integer referenceTaxonId = species.getReferenceTaxonId();
172 Preconditions.checkNotNull(referenceTaxonId, "Can't have a null referenceTaxonId for species: " + species);
173 Species species1 = speciesByReferenceTaxonId.get(referenceTaxonId.toString());
174 consolidateSpecies(speciesBatch, species1);
175
176 }
177
178 }
179
180 return new BigfinImportContext(importFile, operation, catchBatch, signsToCaracteristicValue, speciesBySurveyCode, speciesProtocolBySurveyCode, rootSpeciesBatch);
181
182 }
183
184 protected void consolidateSpecies(SpeciesBatch speciesBatch, Species species) {
185
186 speciesBatch.setSpecies(species);
187 if (!speciesBatch.isChildBatchsEmpty()) {
188
189 for (SpeciesBatch childBatch : speciesBatch.getChildBatchs()) {
190 consolidateSpecies(childBatch, species);
191 }
192 }
193
194 }
195
196 public BigfinImportResult importFile(File importFile, FishingOperation operation, CatchBatch catchBatch) {
197
198 Preconditions.checkNotNull(importFile);
199 Preconditions.checkArgument(importFile.exists(), "Bigfin file " + importFile + " does not exist.");
200
201 BigfinImportContext importContext = prepareImportContext(importFile, operation, catchBatch);
202
203 BigfinDataRowModel importModel = new BigfinDataRowModel(importContext.speciesBySurveyCode, importContext.getSpeciesBatchesById());
204
205 try (Reader reader = Files.newReader(importFile, Charsets.UTF_8)) {
206
207 try (Import<BigfinDataRow> importer = Import.newImport(importModel, reader)) {
208
209 for (BigfinDataRow bigfinDataRow : importer) {
210
211 if (log.isInfoEnabled()) {
212 log.info("Check row: " + bigfinDataRow.getRecordId());
213 }
214 boolean rowIsSafe = importContext.checkRow(bigfinDataRow);
215
216 if (rowIsSafe) {
217
218 importContext.addRowToProcess(bigfinDataRow);
219 }
220
221 }
222
223 }
224
225 } catch (IOException e) {
226 throw new ImportRuntimeException("Could not import bigfin data from file " + importFile, e);
227
228 }
229
230 if (importContext.isNoError()) {
231
232 processSpeciesBatchRows(importContext);
233
234 processSpeciesRows(importContext);
235
236 addFileAsAttachment(importFile, importContext.catchBatch);
237
238 }
239
240 return importContext.getResult();
241
242 }
243
244
245 private void processSpeciesBatchRows(BigfinImportContext importContext) {
246
247 BigfinImportResult result = importContext.getResult();
248
249 Multimap<SpeciesBatch, BigfinDataRow> speciesBatchRowsBySpeciesBatch = importContext.getSpeciesBatchRowsBySpeciesBatch();
250
251 for (SpeciesBatch batch : speciesBatchRowsBySpeciesBatch.keySet()) {
252
253 List<SpeciesBatchFrequency> existingSpeciesFrequencies = persistenceService.getAllSpeciesBatchFrequency(batch.getIdAsInt());
254
255 Integer deletedNb = persistenceService.countFrequenciesNumber(existingSpeciesFrequencies, false);
256
257 Collection<BigfinDataRow> bigfinDataRows = speciesBatchRowsBySpeciesBatch.get(batch);
258
259 Species species = importContext.getSpeciesWithSurveyCode(batch.getSpecies());
260 Caracteristic lengthStepPmfm = importContext.getLengthStepPmfm(species, persistenceService);
261 List<SpeciesBatchFrequency> frequencies = createFrequencies(batch, bigfinDataRows, lengthStepPmfm);
262
263 persistenceService.saveSpeciesBatchFrequency(batch.getIdAsInt(), frequencies);
264
265 result.incrementNbFrequenciesDeleted(deletedNb != null ? deletedNb : 0);
266
267 Integer importedNb = persistenceService.countFrequenciesNumber(frequencies, false);
268 result.incrementNbFrequenciesImported(importedNb != null ? importedNb : 0);
269
270 }
271
272 }
273
274 private void processSpeciesRows(BigfinImportContext importContext) {
275
276 BigfinImportResult result = importContext.getResult();
277
278 SampleCategoryModel sampleCategoryModel = context.getSampleCategoryModel();
279 List<Integer> samplingOrder = sampleCategoryModel.getSamplingOrder();
280
281 List<Integer> pmfmIds = new ArrayList<>();
282 pmfmIds.add(PmfmId.SORTED_UNSORTED.getValue());
283 List<Function<BigfinDataRow, Sign>> functions = new ArrayList<>();
284
285
286 for (Integer categoryId : samplingOrder) {
287 if (PmfmId.SIZE_CATEGORY.getValue().equals(categoryId)) {
288 pmfmIds.add(categoryId);
289 functions.add(Size.newExtractValueFunction());
290
291 } else if (PmfmId.SEX.getValue().equals(categoryId)) {
292 pmfmIds.add(categoryId);
293 functions.add(Sex.newExtractValueFunction());
294 }
295 }
296
297 List<Category> categories = new ArrayList<>();
298 for (int i = 0; i < pmfmIds.size(); i++) {
299 Category category = new Category(pmfmIds.get(i), i < functions.size() ? functions.get(i) : null);
300 categories.add(category);
301 }
302
303
304 Multimap<Species, SpeciesBatch> batchesBySpecies = importContext.getRootSpeciesBatchBySpecies();
305
306
307 Multimap<Species, BigfinDataRow> rowsBySpecies = importContext.getSpeciesRowsBySpecies();
308
309
310 for (Species species : rowsBySpecies.keySet()) {
311
312
313 Caracteristic lengthStepPmfm = importContext.getLengthStepPmfm(species, persistenceService);
314
315
316 Collection<BigfinDataRow> speciesRows = rowsBySpecies.get(species);
317 Multimap<Sign, BigfinDataRow> rowsByVracHorsVrac = Multimaps.index(speciesRows, VracHorsVrac.newExtractValueFunction());
318
319
320 Collection<SpeciesBatch> speciesBatches = batchesBySpecies.get(species);
321 Map<Serializable, SpeciesBatch> speciesBatchByVracHorsVrac = Maps.uniqueIndex(speciesBatches, SpeciesBatchs.GET_SAMPLE_CATEGORY_VALUE);
322
323 BrowseBatchesParameter commonParameter = new BrowseBatchesParameter(importContext.operation, species, lengthStepPmfm, categories, result);
324 browseBatchesToAddFrequencies(commonParameter, null, 0, speciesBatchByVracHorsVrac, rowsByVracHorsVrac);
325
326 }
327
328 }
329
330
331
332
333
334
335
336
337
338
339
340 protected void browseBatchesToAddFrequencies(BrowseBatchesParameter commonParameter,
341 SpeciesBatch parentBatch,
342 int depth,
343 Map<Serializable, SpeciesBatch> batchesByCaracteristic,
344 Multimap<Sign, BigfinDataRow> rowsByCaracteristic) {
345
346 Category category = commonParameter.getCategories().get(depth++);
347
348 int parentSignChildrenNb = rowsByCaracteristic.keySet().size();
349
350 for (Sign caracteristic : rowsByCaracteristic.keySet()) {
351 Collection<BigfinDataRow> bigfinDataRows = rowsByCaracteristic.get(caracteristic);
352
353
354 SpeciesBatch batch = batchesByCaracteristic.get(signsToCaracteristicValue.get(caracteristic));
355
356 boolean batchHasFrequencies = false;
357
358
359 if (batch == null) {
360 if (caracteristic.isNullEquivalent(parentSignChildrenNb)) {
361 batch = parentBatch;
362
363 } else {
364 batch = createSpeciesBatch(commonParameter.getSpecies(),
365 commonParameter.getOperation(),
366 category.getPmfmId(),
367 caracteristic,
368 parentBatch != null ? parentBatch.getIdAsInt() : null);
369
370 batch.setParentBatch(parentBatch);
371 }
372
373 } else {
374
375 batch.setFishingOperation(commonParameter.getOperation());
376 List<SpeciesBatchFrequency> frequencies = persistenceService.getAllSpeciesBatchFrequency(batch.getIdAsInt());
377 batchHasFrequencies = CollectionUtils.isNotEmpty(frequencies);
378 }
379
380
381 if (category.getCategoryValueGetter() == null) {
382
383 if (CollectionUtils.isNotEmpty(batch.getChildBatchs())) {
384 commonParameter.getResult().addWarning(t("tutti.service.bigfinImport.warning.species.tooCategorized",
385 commonParameter.getSpeciesLabel(),
386 sizeCaracteristic.getParameterName(),
387 sexCaracteristic.getParameterName()));
388
389 } else {
390
391 Integer deletedNb = persistenceService.countFrequenciesNumber(
392 persistenceService.getAllSpeciesBatchFrequency(batch.getIdAsInt()), false);
393
394 List<SpeciesBatchFrequency> frequencies = createFrequencies(batch, bigfinDataRows, commonParameter.getLengthStepPmfm());
395
396 persistenceService.saveSpeciesBatchFrequency(batch.getIdAsInt(), frequencies);
397
398 commonParameter.getResult().incrementNbFrequenciesDeleted(deletedNb != null ? deletedNb : 0);
399
400 Integer importedNb = persistenceService.countFrequenciesNumber(frequencies, false);
401 commonParameter.getResult().incrementNbFrequenciesImported(importedNb != null ? importedNb : 0);
402 }
403
404 } else if (batchHasFrequencies) {
405 CaracteristicQualitativeValue qualitativeValue = signsToCaracteristicValue.get(caracteristic);
406 commonParameter.getResult().addWarning(t("tutti.service.bigfinImport.warning.species.batch.frequenciesOnHigherLevel",
407 commonParameter.getSpeciesLabel(), qualitativeValue.getName()));
408
409 } else {
410 List<SpeciesBatch> batchChildren = batch.getChildBatchs();
411
412 Multimap<Sign, BigfinDataRow> rowsByNewCaracteristic = Multimaps.index(bigfinDataRows, category.getCategoryValueGetter());
413
414
415 Map<Serializable, SpeciesBatch> childrenByCaracteristic = new HashMap<>();
416 if (CollectionUtils.isNotEmpty(batchChildren)) {
417
418 SpeciesBatch firstBatch = batchChildren.get(0);
419 Integer categoryId = firstBatch.getSampleCategoryId();
420 Category nextCategory = commonParameter.getCategories().get(depth);
421
422
423 if (!nextCategory.getPmfmId().equals(categoryId)) {
424
425
426
427 Set<Sign> signsSet = rowsByNewCaracteristic.keySet();
428 if (signsSet.size() == 1 && signsSet.iterator().next().isNullEquivalent(parentSignChildrenNb)) {
429 category = commonParameter.getCategories().get(depth++);
430 rowsByNewCaracteristic = Multimaps.index(bigfinDataRows, category.getCategoryValueGetter());
431
432
433 nextCategory = commonParameter.getCategories().get(depth);
434 if (!nextCategory.getPmfmId().equals(categoryId)) {
435 commonParameter.getResult().addWarning(t("tutti.service.bigfinImport.warning.species.categoriesSkipped",
436 commonParameter.getSpeciesLabel(),
437 sizeCaracteristic.getParameterName(),
438 sexCaracteristic.getParameterName()
439 ));
440 continue;
441 }
442
443 } else {
444 commonParameter.getResult().addWarning(t("tutti.service.bigfinImport.warning.species.categorySkipped",
445 commonParameter.getSpeciesLabel(),
446 persistenceService.getCaracteristic(nextCategory.getPmfmId()).getParameterName()
447 ));
448 continue;
449 }
450 }
451 childrenByCaracteristic.putAll(Maps.uniqueIndex(batchChildren, SpeciesBatchs.GET_SAMPLE_CATEGORY_VALUE));
452 }
453
454
455 browseBatchesToAddFrequencies(commonParameter, batch, depth, childrenByCaracteristic, rowsByNewCaracteristic);
456 }
457
458 }
459 }
460
461 protected SpeciesBatch createSpeciesBatch(Species species,
462 FishingOperation operation,
463 Integer categoryId,
464 Sign signs,
465 Integer parentBatchId) {
466
467 Preconditions.checkArgument(signs.getCategory().equals(categoryId));
468
469 SpeciesBatch batch = SpeciesBatchs.newSpeciesBatch();
470 batch.setSpecies(species);
471 batch.setFishingOperation(operation);
472
473 batch.setSampleCategoryId(categoryId);
474 batch.setSampleCategoryValue(signsToCaracteristicValue.get(signs));
475
476 batch = persistenceService.createSpeciesBatch(batch, parentBatchId, true);
477 return batch;
478 }
479
480 protected List<SpeciesBatchFrequency> createFrequencies(SpeciesBatch batch, Collection<BigfinDataRow> rows, Caracteristic lengthStepPmfm) {
481 Preconditions.checkNotNull(lengthStepPmfm);
482 String unit = lengthStepPmfm.getUnit();
483 Float precision = lengthStepPmfm.getPrecision();
484
485
486
487
488
489
490
491 ListMultimap<Float, Float> weightsByLengthStep = ArrayListMultimap.create();
492 for (BigfinDataRow row : rows) {
493 Float weight = row.getWeight();
494 float length = row.getLength();
495
496 if ("cm".equals(unit)) {
497
498 length = length / 10;
499 }
500
501 int intValue = (int) (length * 10);
502 int intStep = (int) (precision * 10);
503 int correctIntStep = intValue - (intValue % intStep);
504 float lengthStep = correctIntStep / 10f;
505
506 weightsByLengthStep.put(lengthStep, weight);
507 }
508
509
510 Collection<Float> weightValues = weightsByLengthStep.values();
511 int weightValuesSize = weightValues.size();
512
513
514 Collection<Float> notNullWeights = Collections2.filter(weightValues, input -> input != null);
515 int notNullWeightsSize = notNullWeights.size();
516
517
518 if (notNullWeightsSize == 1) {
519 float weight = IterableUtils.get(notNullWeights, 0);
520 weight = WeightUnit.KG.round(weight / 1000);
521 batch.setWeight(weight);
522 persistenceService.saveSpeciesBatch(batch);
523 }
524
525
526 boolean importWeights = weightValuesSize == notNullWeightsSize;
527
528 List<SpeciesBatchFrequency> frequencies = new ArrayList<>();
529 for (Float lengthStep : weightsByLengthStep.keySet()) {
530 SpeciesBatchFrequency frequency = SpeciesBatchFrequencys.newSpeciesBatchFrequency();
531 frequencies.add(frequency);
532
533 frequency.setBatch(batch);
534 frequency.setLengthStep(lengthStep);
535 frequency.setLengthStepCaracteristic(lengthStepPmfm);
536
537 Collection<Float> weights = weightsByLengthStep.get(lengthStep);
538 frequency.setNumber(weights.size());
539
540 if (importWeights) {
541
542 float totalWeight = 0f;
543 for (float weight : weights) {
544 totalWeight += weight;
545 }
546 if (totalWeight > 0f) {
547
548 totalWeight = WeightUnit.KG.round(totalWeight / 1000);
549 frequency.setWeight(totalWeight);
550 }
551 }
552 }
553 return frequencies;
554 }
555
556 protected void addFileAsAttachment(File f, CatchBatch catchBatch) {
557 Attachment attachment = Attachments.newAttachment();
558 attachment.setObjectType(ObjectTypeCode.CATCH_BATCH);
559 attachment.setObjectId(Integer.valueOf(catchBatch.getId()));
560 attachment.setName(f.getName());
561 String date = DateFormat.getDateTimeInstance().format(context.currentDate());
562 String comment = t("tutti.service.bigfin.import.attachment.comment", date);
563 attachment.setComment(comment);
564 persistenceService.createAttachment(attachment, f);
565 }
566
567
568
569
570 private class Category {
571
572 private Integer pmfmId;
573
574
575 private Function<BigfinDataRow, Sign> categoryValueGetter;
576
577 public Category(Integer pmfmId, Function<BigfinDataRow, Sign> dataGetter) {
578 this.pmfmId = pmfmId;
579 this.categoryValueGetter = dataGetter;
580 }
581
582 public Integer getPmfmId() {
583 return pmfmId;
584 }
585
586 public Function<BigfinDataRow, Sign> getCategoryValueGetter() {
587 return categoryValueGetter;
588 }
589 }
590
591
592
593
594
595 private class BrowseBatchesParameter {
596
597 private FishingOperation operation;
598
599
600 private Species species;
601
602
603 private Caracteristic lengthStepPmfm;
604
605
606 private List<Category> categories;
607
608
609 private BigfinImportResult result;
610
611
612 private String speciesLabel;
613
614 public BrowseBatchesParameter(FishingOperation operation, Species species, Caracteristic lengthStepPmfm,
615 List<Category> categories, BigfinImportResult result) {
616 this.operation = operation;
617 this.species = species;
618 this.lengthStepPmfm = lengthStepPmfm;
619 this.categories = categories;
620 this.result = result;
621
622 speciesLabel = species.getSurveyCode();
623 if (StringUtils.isBlank(speciesLabel)) {
624 speciesLabel = species.getRefTaxCode();
625 }
626 }
627
628 public FishingOperation getOperation() {
629 return operation;
630 }
631
632 public Species getSpecies() {
633 return species;
634 }
635
636 public Caracteristic getLengthStepPmfm() {
637 return lengthStepPmfm;
638 }
639
640 public List<Category> getCategories() {
641 return categories;
642 }
643
644 public BigfinImportResult getResult() {
645 return result;
646 }
647
648 public String getSpeciesLabel() {
649 return speciesLabel;
650 }
651 }
652 }