Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import de.symeda.sormas.api.person.PersonReferenceDto;
import de.symeda.sormas.api.person.PresentCondition;
import de.symeda.sormas.api.person.SymptomJournalStatus;
import de.symeda.sormas.api.sample.PathogenTestResultType;
import de.symeda.sormas.api.share.ExternalShareCriteria;
import de.symeda.sormas.api.survey.SurveyReferenceDto;
import de.symeda.sormas.api.user.UserReferenceDto;
Expand Down Expand Up @@ -92,10 +93,14 @@ public class CaseCriteria extends CriteriaWithDateType implements ExternalShareC
public static final String SURVEY_ASSIGNED_TO = "surveyAssignedTo";
public static final String SURVEY_RESPONSE_STATUS = "surveyResponseStatus";
public static final String SURVEY = "survey";
public static final String PATHOGEN_TEST_RESULT = "pathogenTestResult";
public static final String SEROGROUP = "serogroup";

private UserRoleReferenceDto reportingUserRole;
private Disease disease;
private DiseaseVariant diseaseVariant;
private PathogenTestResultType pathogenTestResult;
private String serogroup;
private CaseOutcome outcome;
private CaseClassification caseClassification;
private InvestigationStatus investigationStatus;
Expand Down Expand Up @@ -235,6 +240,33 @@ public DiseaseVariant getDiseaseVariant() {
return diseaseVariant;
}

public void setPathogenTestResult(PathogenTestResultType pathogenTestResult) {
this.pathogenTestResult = pathogenTestResult;
}

public CaseCriteria pathogenTestResult(PathogenTestResultType pathogenTestResult) {
setPathogenTestResult(pathogenTestResult);
return this;
}

public PathogenTestResultType getPathogenTestResult() {
return pathogenTestResult;
}

public void setSerogroup(String serogroup) {
this.serogroup = serogroup;
}

public CaseCriteria serogroup(String serogroup) {
setSerogroup(serogroup);
return this;
}

@IgnoreForUrl
public String getSerogroup() {
return serogroup;
}

public void setJurisdictionType(CaseJurisdictionType jurisdictionType) {
this.jurisdictionType = jurisdictionType;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@

package de.symeda.sormas.api.dashboard;

import de.symeda.sormas.api.disease.DiseaseVariant;
import de.symeda.sormas.api.environment.environmentsample.EnvironmentSampleMaterial;
import de.symeda.sormas.api.sample.PathogenTestResultType;
import de.symeda.sormas.api.sample.SampleDashboardFilterDateType;
import de.symeda.sormas.api.sample.SampleMaterial;

Expand All @@ -24,6 +26,9 @@ public class SampleDashboardCriteria extends BaseDashboardCriteria<SampleDashboa
private SampleDashboardFilterDateType sampleDateType;
private SampleMaterial sampleMaterial;
private EnvironmentSampleMaterial environmentSampleMaterial;
private PathogenTestResultType pathogenTestResult;
private String serogroup;
private DiseaseVariant diseaseVariant;

private Boolean withNoDisease;

Expand Down Expand Up @@ -60,12 +65,44 @@ public SampleDashboardCriteria withNoDisease(Boolean withNoDisease) {

return self;
}

public EnvironmentSampleMaterial getEnvironmentSampleMaterial() {
return environmentSampleMaterial;
}

public SampleDashboardCriteria environmentSampleMaterial(EnvironmentSampleMaterial environmentSampleMaterial) {
this.environmentSampleMaterial = environmentSampleMaterial;

return self;
}

public PathogenTestResultType getPathogenTestResult() {
return pathogenTestResult;
}

public SampleDashboardCriteria pathogenTestResult(PathogenTestResultType pathogenTestResult) {
this.pathogenTestResult = pathogenTestResult;

return self;
}

public String getSerogroup() {
return serogroup;
}

public SampleDashboardCriteria serogroup(String serogroup) {
this.serogroup = serogroup;

return self;
}

public DiseaseVariant getDiseaseVariant() {
return diseaseVariant;
}

public SampleDashboardCriteria diseaseVariant(DiseaseVariant diseaseVariant) {
this.diseaseVariant = diseaseVariant;

return self;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2893,6 +2893,7 @@ public interface Captions {
String selfReportDeletedEnvironments = "selfReportDeletedEnvironments";
String selfReportProcess = "selfReportProcess";
String selfReportSelfReportsList = "selfReportSelfReportsList";
String serogroup = "serogroup";
String sex = "sex";
String showPlacesOnMap = "showPlacesOnMap";
String singleDayEventDate = "singleDayEventDate";
Expand Down
1 change: 1 addition & 0 deletions sormas-api/src/main/resources/captions.properties
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ epiWeekTo=To Epi Week
facilityType=Facility type
facilityTypeGroup=Facility category
featureConfiguration=Feature Configuration
serogroup=Serogroup
firstName=First name
sex=Sex
nationalHealthId=National health ID
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@
import de.symeda.sormas.backend.location.Location;
import de.symeda.sormas.backend.person.Person;
import de.symeda.sormas.backend.person.PersonQueryContext;
import de.symeda.sormas.backend.sample.PathogenTest;
import de.symeda.sormas.backend.sample.Sample;
import de.symeda.sormas.backend.sample.SampleJoins;
import de.symeda.sormas.backend.sample.SampleService;
Expand Down Expand Up @@ -706,6 +707,28 @@ public <T extends AbstractDomainObject> Predicate createCriteriaFilter(CaseCrite
filter =
CriteriaBuilderHelper.and(cb, filter, cb.equal(from.get(Case.DISEASE_VARIANT_VALUE), caseCriteria.getDiseaseVariant().getValue()));
}
if (caseCriteria.getPathogenTestResult() != null) {
Subquery<Long> resultSubquery = cq.subquery(Long.class);
Root<Sample> sampleRoot = resultSubquery.from(Sample.class);
resultSubquery.select(sampleRoot.get(AbstractDomainObject.ID));
resultSubquery.where(
cb.equal(sampleRoot.get(Sample.ASSOCIATED_CASE), from),
cb.equal(sampleRoot.get(Sample.PATHOGEN_TEST_RESULT), caseCriteria.getPathogenTestResult()),
cb.isFalse(sampleRoot.get(DeletableAdo.DELETED)));
filter = CriteriaBuilderHelper.and(cb, filter, cb.exists(resultSubquery));
}
if (StringUtils.isNotBlank(caseCriteria.getSerogroup())) {
Subquery<Long> serogroupSubquery = cq.subquery(Long.class);
Root<Sample> sampleRoot = serogroupSubquery.from(Sample.class);
Join<Sample, PathogenTest> pathogenTestJoin = sampleRoot.join(Sample.PATHOGENTESTS, JoinType.INNER);
serogroupSubquery.select(sampleRoot.get(AbstractDomainObject.ID));
serogroupSubquery.where(
cb.equal(sampleRoot.get(Sample.ASSOCIATED_CASE), from),
CriteriaBuilderHelper.unaccentedIlike(cb, pathogenTestJoin.get(PathogenTest.SEROTYPE_TEXT), caseCriteria.getSerogroup().trim()),
cb.isFalse(sampleRoot.get(DeletableAdo.DELETED)),
cb.isFalse(pathogenTestJoin.get(DeletableAdo.DELETED)));
filter = CriteriaBuilderHelper.and(cb, filter, cb.exists(serogroupSubquery));
}
if (caseCriteria.getOutcome() != null) {
filter = CriteriaBuilderHelper.and(cb, filter, cb.equal(from.get(Case.OUTCOME), caseCriteria.getOutcome()));
}
Expand Down Expand Up @@ -2045,7 +2068,7 @@ private Predicate getSimilarityFilters(CaseSimilarityCriteria criteria, Criteria

/**
* Performance: May be slow when there are 10000s of cases with similar report date in the same region.
*
*
* @param limit
* null: no limit
*/
Expand Down Expand Up @@ -2338,7 +2361,7 @@ private List<Case> getCasesSetAsDuplicate(Long caseId) {
/**
* Updates the vaccination status of all cases of the specified person and disease using
* vaccination status data derived from immunization records.
*
*
* @param personId
* The ID of the case person whose cases should be updated
* @param disease
Expand All @@ -2352,7 +2375,7 @@ public void updateVaccinationStatuses(Long personId, Disease disease, Vaccinatio

/**
* Updates vaccination statuses using the enhanced determination logic (determined mode).
*
*
* <p>
* This method updates cases with sophisticated vaccination statuses derived from immunization data.
* It uses {@link ImmunizationService#deriveVaccinationStatus} to compute statuses that reflect:
Expand All @@ -2363,12 +2386,12 @@ public void updateVaccinationStatuses(Long personId, Disease disease, Vaccinatio
* <li>Other immunity sources (OTHER status)</li>
* <li>Immunization validity periods (validFrom/validUntil dates)</li>
* </ul>
*
*
* <p>
* The method only updates cases where the derived status differs from the current status,
* and automatically updates the change date timestamp.
* </p>
*
*
* @param personId
* The ID of the person whose cases should be updated
* @param disease
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,15 @@
import javax.persistence.criteria.Selection;
import javax.persistence.criteria.Subquery;

import de.symeda.sormas.api.environment.environmentsample.EnvironmentSampleMaterial;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.jetbrains.annotations.NotNull;

import de.symeda.sormas.api.dashboard.SampleDashboardCriteria;
import de.symeda.sormas.api.dashboard.sample.MapSampleDto;
import de.symeda.sormas.api.dashboard.sample.SampleShipmentStatus;
import de.symeda.sormas.api.environment.environmentsample.EnvironmentSampleCriteria;
import de.symeda.sormas.api.environment.environmentsample.EnvironmentSampleMaterial;
import de.symeda.sormas.api.sample.PathogenTestResultType;
import de.symeda.sormas.api.sample.SampleAssociationType;
import de.symeda.sormas.api.sample.SampleCriteria;
Expand Down Expand Up @@ -150,15 +151,17 @@ public Map<SpecimenCondition, Long> getSampleCountsBySpecimenCondition(SampleDas

public Map<SpecimenCondition, Long> getEnvironmentalSampleCountsBySpecimenCondition(SampleDashboardCriteria dashboardCriteria) {
return getEnvironmentalSampleCountsBySpecimenCondition(
EnvironmentSample.SPECIMEN_CONDITION,
SpecimenCondition.class,
dashboardCriteria,
null);
EnvironmentSample.SPECIMEN_CONDITION,
SpecimenCondition.class,
dashboardCriteria,
null);
}

private Map<SpecimenCondition, Long> getEnvironmentalSampleCountsBySpecimenCondition(String property, Class<SpecimenCondition> propertyType,
SampleDashboardCriteria dashboardCriteria,
BiFunction<CriteriaBuilder, Root<EnvironmentSample>, Predicate> additionalFilters) {
private Map<SpecimenCondition, Long> getEnvironmentalSampleCountsBySpecimenCondition(
String property,
Class<SpecimenCondition> propertyType,
SampleDashboardCriteria dashboardCriteria,
BiFunction<CriteriaBuilder, Root<EnvironmentSample>, Predicate> additionalFilters) {
final CriteriaBuilder cb = em.getCriteriaBuilder();
final CriteriaQuery<Tuple> cq = cb.createTupleQuery();
final Root<EnvironmentSample> sample = cq.from(EnvironmentSample.class);
Expand All @@ -173,9 +176,9 @@ private Map<SpecimenCondition, Long> getEnvironmentalSampleCountsBySpecimenCondi
cq.groupBy(groupingProperty);

return QueryHelper.getResultList(em, cq, null, null, Function.identity())
.stream()
.filter(t -> t.get(0) != null)
.collect(Collectors.toMap(t -> propertyType.cast(t.get(0)), t -> (Long) t.get(1)));
.stream()
.filter(t -> t.get(0) != null)
.collect(Collectors.toMap(t -> propertyType.cast(t.get(0)), t -> (Long) t.get(1)));
}

private <T extends Enum<?>> Map<T, Long> getSampleCountsByEnumProperty(
Expand Down Expand Up @@ -219,8 +222,8 @@ public Map<SampleShipmentStatus, Long> getSampleCountsByShipmentStatus(SampleDas
cq.groupBy(shipped, received);

return QueryHelper.getResultList(em, cq, null, null, Function.identity())
.stream()
.collect(Collectors.toMap(t -> getSampleShipmentStatusByFlags((Boolean) t.get(0), (Boolean) t.get(1)), t -> (Long) t.get(2), Long::sum));
.stream()
.collect(Collectors.toMap(t -> getSampleShipmentStatusByFlags((Boolean) t.get(0), (Boolean) t.get(1)), t -> (Long) t.get(2), Long::sum));
}

public Map<SampleShipmentStatus, Long> getEnvironmentalSampleCountsByShipmentStatus(SampleDashboardCriteria dashboardCriteria) {
Expand All @@ -238,8 +241,8 @@ public Map<SampleShipmentStatus, Long> getEnvironmentalSampleCountsByShipmentSta
cq.groupBy(shipped, received);

return QueryHelper.getResultList(em, cq, null, null, Function.identity())
.stream()
.collect(Collectors.toMap(t -> getSampleShipmentStatusByFlags((Boolean) t.get(0), (Boolean) t.get(1)), t -> (Long) t.get(2), Long::sum));
.stream()
.collect(Collectors.toMap(t -> getSampleShipmentStatusByFlags((Boolean) t.get(0), (Boolean) t.get(1)), t -> (Long) t.get(2), Long::sum));
}

private SampleShipmentStatus getSampleShipmentStatusByFlags(Boolean shipped, Boolean received) {
Expand All @@ -255,7 +258,10 @@ public Map<PathogenTestResultType, Long> getTestResultCountsByResultType(SampleD

cq.multiselect(pathogenTestResult, cb.count(pathogenTestJoin));

final Predicate criteriaFilter = createSampleFilter(new SampleQueryContext(cb, cq, sample), dashboardCriteria);
Predicate criteriaFilter = createSampleFilter(new SampleQueryContext(cb, cq, sample), dashboardCriteria);
if (dashboardCriteria.getPathogenTestResult() != null) {
criteriaFilter = CriteriaBuilderHelper.and(cb, criteriaFilter, cb.equal(pathogenTestResult, dashboardCriteria.getPathogenTestResult()));
}
cq.where(criteriaFilter);

cq.groupBy(pathogenTestResult);
Expand Down Expand Up @@ -286,9 +292,9 @@ public Map<PathogenTestResultType, Long> getEnvironmentalTestResultCountsByResul
cq.groupBy(pathogenTestResult);

return QueryHelper.getResultList(em, cq, null, null, Function.identity())
.stream()
.filter(t -> t.get(0) != null)
.collect(Collectors.toMap(t -> (PathogenTestResultType) t.get(0), t -> (Long) t.get(1)));
.stream()
.filter(t -> t.get(0) != null)
.collect(Collectors.toMap(t -> (PathogenTestResultType) t.get(0), t -> (Long) t.get(1)));
}

/**
Expand All @@ -304,13 +310,14 @@ public Map<EnvironmentSampleMaterial, Long> getEnvironmentalSampleCounts(SampleD
final Root<EnvironmentSample> sample = cq.from(EnvironmentSample.class);
final Path<Object> groupingProperty = sample.get(EnvironmentSample.SAMPLE_MATERIAL);
cq.multiselect(groupingProperty, cb.count(sample));
final Predicate criteriaFilter = createEnvironmentSampleFilter(new EnvironmentSampleQueryContext(cb, cq, sample, new EnvironmentSampleJoins(sample)), dashboardCriteria);
final Predicate criteriaFilter =
createEnvironmentSampleFilter(new EnvironmentSampleQueryContext(cb, cq, sample, new EnvironmentSampleJoins(sample)), dashboardCriteria);
cq.where(criteriaFilter);
cq.groupBy(groupingProperty);
return QueryHelper.getResultList(em, cq, null, null, Function.identity())
.stream()
.filter(t -> t.get(0) != null)
.collect(Collectors.toMap(t -> (EnvironmentSampleMaterial) t.get(0), t -> (Long) t.get(1)));
.stream()
.filter(t -> t.get(0) != null)
.collect(Collectors.toMap(t -> (EnvironmentSampleMaterial) t.get(0), t -> (Long) t.get(1)));
}

private static <J extends ISampleJoins> List<Selection<?>> getCoordinatesSelection(
Expand Down Expand Up @@ -510,6 +517,32 @@ private <T extends AbstractDomainObject> Predicate createSampleFilter(SampleQuer
filter = CriteriaBuilderHelper.and(cb, filter, cb.equal(sampleRoot.get(Sample.SAMPLE_MATERIAL), criteria.getSampleMaterial()));
}

// Test result is denormalized on the sample (final lab result) → direct equality, no join.
if (criteria.getPathogenTestResult() != null) {
filter = CriteriaBuilderHelper.and(cb, filter, cb.equal(sampleRoot.get(Sample.PATHOGEN_TEST_RESULT), criteria.getPathogenTestResult()));
}

if (StringUtils.isNotBlank(criteria.getSerogroup())) {
Subquery<Long> serogroupSubquery = cq.subquery(Long.class);
Root<PathogenTest> pathogenTestRoot = serogroupSubquery.from(PathogenTest.class);
serogroupSubquery.select(pathogenTestRoot.get(PathogenTest.ID));
serogroupSubquery.where(
cb.equal(pathogenTestRoot.get(PathogenTest.SAMPLE), sampleRoot),
CriteriaBuilderHelper.unaccentedIlike(cb, pathogenTestRoot.get(PathogenTest.SEROTYPE_TEXT), criteria.getSerogroup().trim()),
cb.isFalse(pathogenTestRoot.get(PathogenTest.DELETED)));
filter = CriteriaBuilderHelper.and(cb, filter, cb.exists(serogroupSubquery));
}
if (criteria.getDiseaseVariant() != null) {
Subquery<Long> variantSubquery = cq.subquery(Long.class);
Root<PathogenTest> pathogenTestRoot = variantSubquery.from(PathogenTest.class);
variantSubquery.select(pathogenTestRoot.get(PathogenTest.ID));
variantSubquery.where(
cb.equal(pathogenTestRoot.get(PathogenTest.SAMPLE), sampleRoot),
cb.equal(pathogenTestRoot.get(PathogenTest.TESTED_DISEASE_VARIANT_VALUE), criteria.getDiseaseVariant().getValue()),
cb.isFalse(pathogenTestRoot.get(PathogenTest.DELETED)));
filter = CriteriaBuilderHelper.and(cb, filter, cb.exists(variantSubquery));
}

if (Boolean.TRUE.equals(criteria.getWithNoDisease())) {
filter = CriteriaBuilderHelper.and(cb, filter, cb.isNotNull(joins.getEventParticipant()), cb.isNull(joins.getEvent().get(Event.DISEASE)));
} else if (Boolean.FALSE.equals(criteria.getWithNoDisease())) {
Expand Down Expand Up @@ -573,7 +606,8 @@ private Predicate createEnvironmentSampleFilter(EnvironmentSampleQueryContext qu
}

if (criteria.getEnvironmentSampleMaterial() != null) {
filter = CriteriaBuilderHelper.and(cb, filter, cb.equal(sampleRoot.get(EnvironmentSample.SAMPLE_MATERIAL), criteria.getEnvironmentSampleMaterial()));
filter = CriteriaBuilderHelper
.and(cb, filter, cb.equal(sampleRoot.get(EnvironmentSample.SAMPLE_MATERIAL), criteria.getEnvironmentSampleMaterial()));
}

return CriteriaBuilderHelper.and(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ public class PathogenTest extends DeletableAdo {
public static final String TEST_RESULT_VERIFIED = "testResultVerified";
public static final String FOUR_FOLD_INCREASE_ANTIBODY_TITER = "fourFoldIncreaseAntibodyTiter";
public static final String SEROTYPE = "serotype";
public static final String SEROTYPE_TEXT = "serotypeText";
public static final String CQ_VALUE = "cqValue";
public static final String CT_VALUE_E = "ctValueE";
public static final String CT_VALUE_N = "ctValueN";
Expand Down
Loading
Loading