diff --git a/src/main/java/org/ohdsi/webapi/person/CohortPerson.java b/src/main/java/org/ohdsi/webapi/person/CohortPerson.java new file mode 100644 index 000000000..58135d2d7 --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/person/CohortPerson.java @@ -0,0 +1,29 @@ +/* + * Copyright 2015 fdefalco. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ohdsi.webapi.person; + +import java.sql.Timestamp; + +/** + * + * @author fdefalco + */ +public class CohortPerson { + public Long personId; + public Long cohortDefinitionId; + public Timestamp startDate; + public Timestamp endDate; +} diff --git a/src/main/java/org/ohdsi/webapi/person/Era.java b/src/main/java/org/ohdsi/webapi/person/Era.java new file mode 100644 index 000000000..664960a24 --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/person/Era.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015 fdefalco. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ohdsi.webapi.person; + +import java.sql.Timestamp; + +/** + * + * @author fdefalco + */ +public class Era { + public Timestamp startDate; + public Timestamp endDate; +} diff --git a/src/main/java/org/ohdsi/webapi/person/EraSet.java b/src/main/java/org/ohdsi/webapi/person/EraSet.java new file mode 100644 index 000000000..91bb16f5b --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/person/EraSet.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015 fdefalco. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ohdsi.webapi.person; + +import java.util.ArrayList; + +/** + * + * @author fdefalco + */ +public class EraSet { + public ArrayList eras; + public Long conceptId; + public String conceptName; + public String eraType; + + public EraSet() { + eras = new ArrayList<>(); + } +} diff --git a/src/main/java/org/ohdsi/webapi/person/ObservationPeriod.java b/src/main/java/org/ohdsi/webapi/person/ObservationPeriod.java new file mode 100644 index 000000000..5c599bba5 --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/person/ObservationPeriod.java @@ -0,0 +1,35 @@ +/* + * Copyright 2016 fdefalco. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ohdsi.webapi.person; + +import java.sql.Timestamp; + +/** + * + * @author fdefalco + */ +public class ObservationPeriod { + public ObservationPeriod() { + + } + + public long id; + public Timestamp startDate; + public Timestamp endDate; + public String type; + public int x1; + public int x2; +} diff --git a/src/main/java/org/ohdsi/webapi/person/PersonConfigurationInfo.java b/src/main/java/org/ohdsi/webapi/person/PersonConfigurationInfo.java new file mode 100644 index 000000000..7eb18772e --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/person/PersonConfigurationInfo.java @@ -0,0 +1,22 @@ +package org.ohdsi.webapi.person; + +import org.ohdsi.info.ConfigurationInfo; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class PersonConfigurationInfo extends ConfigurationInfo { + + private static final String KEY = "person"; + + public PersonConfigurationInfo(@Value("${person.viewDates}") Boolean viewDatesPermitted) { + + properties.put("viewDatesPermitted", viewDatesPermitted); + } + + @Override + public String getKey() { + + return KEY; + } +} diff --git a/src/main/java/org/ohdsi/webapi/person/PersonProfile.java b/src/main/java/org/ohdsi/webapi/person/PersonProfile.java new file mode 100644 index 000000000..81dd15a9a --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/person/PersonProfile.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015 fdefalco. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ohdsi.webapi.person; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.ArrayList; + +/** + * + * @author fdefalco + */ +public class PersonProfile { + + public ArrayList records; + public ArrayList cohorts; + public ArrayList observationPeriods; + + public String gender; + public int yearOfBirth; + public int ageAtIndex; + + public PersonProfile() { + records = new ArrayList<>(); + cohorts = new ArrayList<>(); + observationPeriods = new ArrayList<>(); + } + + @JsonProperty("recordCount") + public Integer getRecordCount() { + return this.records.size(); + } + +} diff --git a/src/main/java/org/ohdsi/webapi/person/PersonRecord.java b/src/main/java/org/ohdsi/webapi/person/PersonRecord.java new file mode 100644 index 000000000..b7d0d2331 --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/person/PersonRecord.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015 fdefalco. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ohdsi.webapi.person; + +import java.sql.Timestamp; + +/** + * + * @author fdefalco + */ +public class PersonRecord { + public String domain; + public Long conceptId; + public String conceptName; + public Timestamp startDate; + public Timestamp endDate; + public int startDay; + public int endDay; +} diff --git a/src/main/java/org/ohdsi/webapi/person/PersonService.java b/src/main/java/org/ohdsi/webapi/person/PersonService.java new file mode 100644 index 000000000..24cf3ca8f --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/person/PersonService.java @@ -0,0 +1,196 @@ +/* + * Copyright 2015 fdefalco. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ohdsi.webapi.person; + +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.http.MediaType; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.security.access.prepost.PreAuthorize; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.Comparator; +import java.util.Objects; +import java.util.Optional; + +import org.ohdsi.webapi.service.AbstractDaoService; +import org.ohdsi.webapi.source.Source; +import org.ohdsi.webapi.source.SourceDaimon; +import org.ohdsi.webapi.util.PreparedStatementRenderer; +import org.springframework.beans.factory.annotation.Value; + +@RestController +@RequestMapping("/{sourceKey}/person") +public class PersonService extends AbstractDaoService { + + @Value("${person.viewDates}") + private Boolean viewDatesPermitted; + + /** + * Get the complete medical history for a single person in a single database + * @summary Get complete patient profile + * @param sourceKey Data source to extract from + * @param personId Person whose profile to extract + * @param cohortId (optional) Cohort used to adjust start and end dates. If the person is a member of the cohort then + * start and end dates will be adjusted so they are relative to the cohort start date. + * @return All records in a patient profile with start and end days relative to cohort start date or initial date of + * observation + */ + @PreAuthorize("hasSourceAccess(#sourceKey, READ)") + @GetMapping(value = "/{personId}", produces = MediaType.APPLICATION_JSON_VALUE) + public PersonProfile getPersonProfile(@PathVariable("sourceKey") String sourceKey, @PathVariable("personId") String personId, + @RequestParam(value = "cohort", defaultValue = "0") Long cohortId) + { + final PersonProfile profile = new PersonProfile(); + + boolean showDates = this.canViewDates(); + + Source source = getSourceRepository().findBySourceKey(sourceKey); + profile.gender = "not found"; + profile.yearOfBirth = 0; + + PreparedStatementRenderer psrPersonInfo = preparePersonInfoSql(personId, source); + getSourceJdbcTemplate(source).query(psrPersonInfo.getSql(), psrPersonInfo.getSetter(), new RowMapper() { + @Override + public Void mapRow(ResultSet resultSet, int arg1) throws SQLException { + profile.yearOfBirth = resultSet.getInt("year_of_birth"); + profile.gender = resultSet.getString("gender"); + return null; + } + }); + if (profile.gender.equals("not found")) { + throw new RuntimeException("Can't find person " + personId); + } + + // get observation periods + PreparedStatementRenderer psrObservationPeriods = prepareObservationPeriodsSql(personId, source); + getSourceJdbcTemplate(source).query(psrObservationPeriods.getSql(), psrObservationPeriods.getSetter(), new RowMapper() { + @Override + public Void mapRow(ResultSet resultSet, int arg1) throws SQLException { + ObservationPeriod op = new ObservationPeriod(); + + op.startDate = resultSet.getTimestamp("start_date"); + op.endDate = resultSet.getTimestamp("end_date"); + op.type = resultSet.getString("observation_period_type"); + op.id = resultSet.getLong("observation_period_id"); + + profile.observationPeriods.add(op); + return null; + } + }); + // get simplified records + PreparedStatementRenderer psrPersonProfile = prepareGetPersonProfile(personId, source); + getSourceJdbcTemplate(source).query(psrPersonProfile.getSql(), psrPersonProfile.getSetter(), new RowMapper() { + + @Override + public Void mapRow(ResultSet resultSet, int arg1) throws SQLException { + PersonRecord item = new PersonRecord(); + + item.conceptId = resultSet.getLong("concept_id"); + item.conceptName = resultSet.getString("concept_name"); + item.domain = resultSet.getString("domain"); + item.startDate = resultSet.getTimestamp("start_date"); + item.endDate = resultSet.getTimestamp("end_date"); + + profile.records.add(item); + return null; + } + }); + + PreparedStatementRenderer psrGetCohorts = prepareGetCohortsSql(personId, source); + getSourceJdbcTemplate(source).query(psrGetCohorts.getSql(), psrGetCohorts.getSetter(), new RowMapper() { + @Override + public Void mapRow(ResultSet resultSet, int arg1) throws SQLException { + CohortPerson item = new CohortPerson(); + + item.startDate = resultSet.getTimestamp("cohort_start_date"); + item.endDate = resultSet.getTimestamp("cohort_end_date"); + item.cohortDefinitionId = resultSet.getLong("cohort_definition_id"); + + profile.cohorts.add(item); + return null; + } + }); + + + LocalDateTime cohortStartDate = null; + Optional cohort = cohortId > 0 ? profile.cohorts.stream().filter(c -> c.cohortDefinitionId.equals(cohortId)).findFirst() : + Optional.empty(); + cohortStartDate = cohort.map(c -> c.startDate.toLocalDateTime()).orElseGet(() -> + profile.records.stream().min(Comparator.comparing(c -> c.startDate)) + .map(r -> r.startDate.toLocalDateTime()).orElse(null)); + + if (cohortStartDate != null && profile.yearOfBirth > 0) { + profile.ageAtIndex = cohortStartDate.getYear() - profile.yearOfBirth; + } + + for(PersonRecord record : profile.records){ + record.startDay = Math.toIntExact(ChronoUnit.DAYS.between(cohortStartDate, record.startDate.toLocalDateTime())); + record.endDay = Objects.nonNull(record.endDate) ? Math.toIntExact(ChronoUnit.DAYS.between(cohortStartDate, + record.endDate.toLocalDateTime())) : record.startDay; + if (!showDates) { + record.startDate = null; + record.endDate = null; + } + } + for(ObservationPeriod period : profile.observationPeriods){ + period.x1 = Math.toIntExact(ChronoUnit.DAYS.between(cohortStartDate, + period.startDate.toLocalDateTime())); + period.x2 = Math.toIntExact(ChronoUnit.DAYS.between(cohortStartDate, + period.endDate.toLocalDateTime())); + if (!showDates) { + period.startDate = null; + period.endDate = null; + } + } + + return profile; + } + + protected PreparedStatementRenderer prepareObservationPeriodsSql(String personId, Source source) { + + String resultsTableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.CDM); + return new PreparedStatementRenderer(source, "/resources/person/sql/getObservationPeriods.sql", "tableQualifier", resultsTableQualifier, "personId", Long.valueOf(personId)); + } + + protected PreparedStatementRenderer prepareGetCohortsSql(String personId, Source source) { + + String resultsTableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.Results); + return new PreparedStatementRenderer(source, "/resources/person/sql/getCohorts.sql", "tableQualifier", resultsTableQualifier, "subjectId", Long.valueOf(personId)); + } + + protected PreparedStatementRenderer preparePersonInfoSql(String personId, Source source) { + + String tableQualifier = source.getTableQualifier(SourceDaimon.DaimonType.CDM); + return new PreparedStatementRenderer(source, "/resources/person/sql/personInfo.sql", "tableQualifier", tableQualifier, "personId", Long.valueOf(personId)); + } + + protected PreparedStatementRenderer prepareGetPersonProfile(String personId, Source source) { + + String tqValue = source.getTableQualifier(SourceDaimon.DaimonType.CDM); + return new PreparedStatementRenderer(source, "/resources/person/sql/getRecords.sql", "tableQualifier", tqValue, "personId", Long.valueOf(personId)); + } + + private Boolean canViewDates() { + return Boolean.TRUE.equals(this.viewDatesPermitted); + } +} diff --git a/src/main/java/org/ohdsi/webapi/person/Timewave.java b/src/main/java/org/ohdsi/webapi/person/Timewave.java new file mode 100644 index 000000000..9050c6baa --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/person/Timewave.java @@ -0,0 +1,28 @@ +/* + * Copyright 2015 fdefalco. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ohdsi.webapi.person; + +import java.util.Collection; + +public class Timewave { + + public Timewave() { + maxEvents = 0; + } + + public Collection buckets; + public Integer maxEvents; +} diff --git a/src/main/java/org/ohdsi/webapi/person/TimewaveBucket.java b/src/main/java/org/ohdsi/webapi/person/TimewaveBucket.java new file mode 100644 index 000000000..5749510e3 --- /dev/null +++ b/src/main/java/org/ohdsi/webapi/person/TimewaveBucket.java @@ -0,0 +1,37 @@ +/* + * Copyright 2015 fdefalco. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ohdsi.webapi.person; + +import java.sql.Timestamp; + +/** + * + * @author fdefalco + */ +public class TimewaveBucket { + + public TimewaveBucket() { + } + + public Timestamp timeIndex; + public Integer drugs = 0; + public Integer conditions = 0; + public Integer observations = 0; + + public Integer getRecords() { + return drugs + conditions + observations; + } +}