Skip to content

Commit

Permalink
Merge pull request #188 from MORE-Platform/549-relative-observation-w…
Browse files Browse the repository at this point in the history
…ith-repeat-events

#549: Refactor SchedulerUtils method signatures to allow relative sch…
  • Loading branch information
janoliver20 authored Dec 11, 2024
2 parents 85e3e14 + ac1968c commit b4ab59a
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 96 deletions.
97 changes: 46 additions & 51 deletions src/main/java/io/redlink/more/data/repository/StudyRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,13 @@
* Copyright (c) 2022 Redlink GmbH.
*/
package io.redlink.more.data.repository;
import org.apache.commons.lang3.tuple.Pair;
import io.redlink.more.data.model.*;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.function.Supplier;

import io.redlink.more.data.model.*;
import io.redlink.more.data.model.scheduler.Interval;
import io.redlink.more.data.model.scheduler.RelativeEvent;
import io.redlink.more.data.model.scheduler.ScheduleEvent;
import io.redlink.more.data.schedule.SchedulerUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
Expand All @@ -27,6 +18,15 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.function.Supplier;

import static io.redlink.more.data.repository.DbUtils.toInstant;
import static io.redlink.more.data.repository.DbUtils.toLocalDate;

Expand Down Expand Up @@ -67,41 +67,41 @@ INNER JOIN studies s on (s.study_id = pt.study_id)

private static final String SQL_INSERT_CREDENTIALS =
"WITH data as (SELECT :api_secret as api_secret, :study_id as study_id, :participant_id as participant_id) " +
"INSERT INTO api_credentials (api_id, api_secret, study_id, participant_id) " +
"SELECT md5(study_id::text || random()::text), api_secret, study_id, participant_id FROM data " +
"RETURNING api_id";
"INSERT INTO api_credentials (api_id, api_secret, study_id, participant_id) " +
"SELECT md5(study_id::text || random()::text), api_secret, study_id, participant_id FROM data " +
"RETURNING api_id";
private static final String SQL_CLEAR_CREDENTIALS =
"DELETE FROM api_credentials " +
"WHERE api_id = :api_id " +
"RETURNING study_id, participant_id";
"WHERE api_id = :api_id " +
"RETURNING study_id, participant_id";

private static final String SQL_INSERT_STUDY_CONSENT =
"INSERT INTO participation_consents(study_id, participant_id, accepted, origin, content_md5) VALUES (:study_id, :participant_id, :accepted, :origin, :content_md5) " +
"ON CONFLICT (study_id, participant_id) DO " +
" UPDATE SET accepted = excluded.accepted, origin = excluded.origin, content_md5 = excluded.content_md5, " +
" consent_timestamp = now(), consent_withdrawn = NULL";
"ON CONFLICT (study_id, participant_id) DO " +
" UPDATE SET accepted = excluded.accepted, origin = excluded.origin, content_md5 = excluded.content_md5, " +
" consent_timestamp = now(), consent_withdrawn = NULL";
private static final String SQL_WITHDRAW_STUDY_CONSENT =
"UPDATE participation_consents " +
"SET consent_withdrawn = now() " +
"WHERE study_id = :study_id AND participant_id = :participant_id";
"SET consent_withdrawn = now() " +
"WHERE study_id = :study_id AND participant_id = :participant_id";

private static final String SQL_INSERT_OBSERVATION_CONSENT =
"INSERT INTO observation_consents(study_id, participant_id, observation_id) VALUES (:study_id, :participant_id, :observation_id) " +
"ON CONFLICT (study_id, participant_id, observation_id) DO NOTHING";
"ON CONFLICT (study_id, participant_id, observation_id) DO NOTHING";
private static final String SQL_SET_PARTICIPANT_STATUS =
"UPDATE participants " +
"SET status = :newStatus::participant_status, start = :start, modified = now() " +
"WHERE study_id = :study_id AND participant_id = :participant_id AND status = :oldStatus::participant_status";
"SET status = :newStatus::participant_status, start = :start, modified = now() " +
"WHERE study_id = :study_id AND participant_id = :participant_id AND status = :oldStatus::participant_status";

private static final String SQL_LIST_PARTICIPANTS_BY_STUDY =
"SELECT participant_id, alias, status, sg.study_group_id, sg.title as study_group_title, start " +
"FROM participants p LEFT OUTER JOIN study_groups sg ON ( p.study_id = sg.study_id AND p.study_group_id = sg.study_group_id ) " +
"WHERE p.study_id = :study_id " +
"AND (p.study_group_id = :study_group_id OR :study_group_id::INT IS NULL)";
"FROM participants p LEFT OUTER JOIN study_groups sg ON ( p.study_id = sg.study_id AND p.study_group_id = sg.study_group_id ) " +
"WHERE p.study_id = :study_id " +
"AND (p.study_group_id = :study_group_id OR :study_group_id::INT IS NULL)";

private static final String GET_OBSERVATION_PROPERTIES_FOR_PARTICIPANT =
"SELECT properties FROM participant_observation_properties " +
"WHERE study_id = ? AND participant_id = ? AND observation_id = ?";
"WHERE study_id = ? AND participant_id = ? AND observation_id = ?";

private static final String GET_API_ROUTING_INFO_BY_API_TOKEN = """
SELECT t.study_id, t.observation_id, o.study_group_id, o.type, t.token,
Expand All @@ -116,14 +116,14 @@ INNER JOIN studies s ON (t.study_id = s.study_id)

private static final String GET_PARTICIPANT_INFO_AND_START_DURATION_END_FOR_STUDY_AND_PARTICIPANT =
"SELECT start, participant_id, alias, COALESCE(sg.duration, s.duration) AS duration, s.planned_end_date FROM participants p " +
"LEFT OUTER JOIN study_groups sg on p.study_id = sg.study_id and p.study_group_id = sg.study_group_id " +
"JOIN studies s on p.study_id = s.study_id " +
"WHERE p.study_id = ? AND participant_id = ?";
"LEFT OUTER JOIN study_groups sg on p.study_id = sg.study_id and p.study_group_id = sg.study_group_id " +
"JOIN studies s on p.study_id = s.study_id " +
"WHERE p.study_id = ? AND participant_id = ?";

private static final String GET_DURATION_INFO_FOR_STUDY =
"SELECT sg.study_group_id as groupid, sg.duration AS groupduration, s.duration AS studyduration, s.planned_end_date AS enddate, s.planned_start_date AS startdate FROM studies s " +
"LEFT OUTER JOIN study_groups sg on s.study_id = sg.study_id " +
"WHERE s.study_id = ?";
"LEFT OUTER JOIN study_groups sg on s.study_id = sg.study_id " +
"WHERE s.study_id = ?";

private final JdbcTemplate jdbcTemplate;
private final NamedParameterJdbcTemplate namedTemplate;
Expand All @@ -147,7 +147,7 @@ private Optional<RoutingInfo> getRoutingInfo(String registrationToken, boolean l
}

public Optional<ApiRoutingInfo> getApiRoutingInfo(Long studyId, Integer observationId, Integer tokenId) {
try(var stream = jdbcTemplate.queryForStream(
try (var stream = jdbcTemplate.queryForStream(
GET_API_ROUTING_INFO_BY_API_TOKEN,
getApiRoutingInfoRowMapper(),
studyId, observationId, tokenId
Expand Down Expand Up @@ -177,7 +177,7 @@ public Optional<Study> findStudy(RoutingInfo routingInfo) {

public Optional<Study> findStudy(RoutingInfo routingInfo, boolean filterObservationsByGroup) {
final List<Observation> observations = listObservations(
routingInfo.studyId(), routingInfo.studyGroupId().orElse(-1), routingInfo.participantId(),filterObservationsByGroup);
routingInfo.studyId(), routingInfo.studyGroupId().orElse(-1), routingInfo.participantId(), filterObservationsByGroup);

final SimpleParticipant participant = findParticipant(routingInfo).orElse(null);

Expand Down Expand Up @@ -216,7 +216,7 @@ public List<Participant> listParticipants(long studyId, OptionalInt groupId) {
}

private List<Observation> listObservations(long studyId, int groupId, int participantId, boolean filterByGroup) {
if(filterByGroup) {
if (filterByGroup) {
return jdbcTemplate.query(SQL_LIST_OBSERVATIONS_BY_STUDY, getObservationRowMapper(), studyId, groupId).stream()
.map(o -> mergeParticipantProperties(o, studyId, participantId))
.toList();
Expand Down Expand Up @@ -248,7 +248,7 @@ public Optional<Object> getParticipantProperties(Long studyId, Integer participa
}

private static RowMapper<Object> getParticipantObservationPropertiesRowMapper() {
return (rs, rowNum) -> DbUtils.readObject(rs,"properties");
return (rs, rowNum) -> DbUtils.readObject(rs, "properties");
}

private static RowMapper<ScheduleEvent> getObservationScheduleRowMapper() {
Expand Down Expand Up @@ -361,7 +361,7 @@ private static RowMapper<Observation> getObservationRowMapper() {
rs.getString("title"),
rs.getString("type"),
rs.getString("participant_info"),
DbUtils.readObject(rs,"properties"),
DbUtils.readObject(rs, "properties"),
DbUtils.readEvent(rs, "schedule"),
toInstant(rs.getTimestamp("created")),
toInstant(rs.getTimestamp("modified")),
Expand Down Expand Up @@ -425,16 +425,11 @@ private static MapSqlParameterSource toParameterSource(long studyId, int partici
}

public Interval getInterval(Long studyId, Integer participantId, RelativeEvent event) {
try(var stream = jdbcTemplate.queryForStream(
try (var stream = jdbcTemplate.queryForStream(
GET_PARTICIPANT_INFO_AND_START_DURATION_END_FOR_STUDY_AND_PARTICIPANT,
((rs, rowNum) -> {
Instant start = rs.getTimestamp("start").toInstant();
// TODO correct sql.Date to Instant with Time 0 ?!
Instant end = Optional.ofNullable(DbUtils.readDuration(rs, "duration"))
.map(d -> d.getEnd(start))
.orElse(Instant.ofEpochMilli(rs.getDate("planned_end_date").getTime()));
return new Interval(start, SchedulerUtils.getEnd(event, start, end));

return new Interval(start, SchedulerUtils.getEnd(event, start));
}),
studyId, participantId
)) {
Expand All @@ -444,12 +439,12 @@ public Interval getInterval(Long studyId, Integer participantId, RelativeEvent e

public Optional<StudyDurationInfo> getStudyDurationInfo(Long studyId) {
return jdbcTemplate.query(GET_DURATION_INFO_FOR_STUDY,
((rs, rowNum) -> new StudyDurationInfo()
.setEndDate(rs.getDate("enddate").toLocalDate())
.setStartDate(rs.getDate("startdate").toLocalDate())
.setDuration(DbUtils.readDuration(rs, "studyduration"))
.addGroupDuration(Pair.of(rs.getInt("groupid"), DbUtils.readDuration(rs, "groupduration"))
)), studyId).stream()
((rs, rowNum) -> new StudyDurationInfo()
.setEndDate(rs.getDate("enddate").toLocalDate())
.setStartDate(rs.getDate("startdate").toLocalDate())
.setDuration(DbUtils.readDuration(rs, "studyduration"))
.addGroupDuration(Pair.of(rs.getInt("groupid"), DbUtils.readDuration(rs, "groupduration"))
)), studyId).stream()
.reduce((prev, curr) -> prev.addGroupDuration(curr.getGroupDurations().get(0)));
}
}
15 changes: 7 additions & 8 deletions src/main/java/io/redlink/more/data/schedule/SchedulerUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@

public class SchedulerUtils {

public static Instant getEnd(RelativeEvent event, Instant start, Instant end) {
return parseToObservationSchedulesForRelativeEvent(event, start, end)
public static Instant getEnd(RelativeEvent event, Instant start) {
return parseToObservationSchedulesForRelativeEvent(event, start)
.stream().map(Range::getMaximum).max(Instant::compareTo).orElse(null);
}

public static List<Range<Instant>> parseToObservationSchedulesForRelativeEvent(
RelativeEvent event, Instant start, Instant maxEnd) {
RelativeEvent event, Instant start) {

final List<Range<Instant>> events = new ArrayList<>();

Expand All @@ -42,13 +42,12 @@ public static List<Range<Instant>> parseToObservationSchedulesForRelativeEvent(
if (event.getRrrule() != null) {
RelativeRecurrenceRule rrule = event.getRrrule();
Instant maxEndOfRule = currentEvt.getMaximum().plus(rrule.getEndAfter().getValue(), rrule.getEndAfter().getUnit().toTemporalUnit());
maxEnd = maxEnd.isBefore(maxEndOfRule) ? maxEnd : maxEndOfRule;
long durationInMs = currentEvt.getMaximum().toEpochMilli() - currentEvt.getMinimum().toEpochMilli();

while (currentEvt.getMaximum().isBefore(maxEnd)) {
while (currentEvt.getMaximum().isBefore(maxEndOfRule)) {
events.add(currentEvt);
Instant estart = currentEvt.getMinimum().plus(rrule.getFrequency().getValue(), rrule.getFrequency().getUnit().toTemporalUnit());
currentEvt = Range.of(estart, estart.plusMillis(durationInMs));
Instant eventStart = currentEvt.getMinimum().plus(rrule.getFrequency().getValue(), rrule.getFrequency().getUnit().toTemporalUnit());
currentEvt = Range.of(eventStart, eventStart.plusMillis(durationInMs));
}
} else {
events.add(currentEvt);
Expand Down Expand Up @@ -90,7 +89,7 @@ public static List<Range<Instant>> parseToObservationSchedules(ScheduleEvent sch
if (scheduleEvent instanceof Event event) {
return parseToObservationSchedulesForEvent(event, start, end);
} else if (scheduleEvent instanceof RelativeEvent relativeEvent) {
return parseToObservationSchedulesForRelativeEvent(relativeEvent, start, end);
return parseToObservationSchedulesForRelativeEvent(relativeEvent, start);
} else {
return Collections.emptyList();
}
Expand Down
Loading

0 comments on commit b4ab59a

Please sign in to comment.