Skip to content

Commit

Permalink
fix: fix deidentied use case for date range into patient and encounter
Browse files Browse the repository at this point in the history
* release: release version 2.29.2

* fix: fix deidentied use case for date range into patient and encounter
  • Loading branch information
Mehdi-BOUYAHIA authored Jan 2, 2024
1 parent 6d284a5 commit 5bb121d
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import KeyboardBackspaceIcon from '@mui/icons-material/KeyboardBackspace'

import useStyles from './styles'

import { useAppSelector } from 'state'

import { DurationRangeType, LabelObject, VitalStatusLabel } from 'types/searchCriterias'
import CalendarRange from 'components/ui/Inputs/CalendarRange'
import DurationRange from 'components/ui/Inputs/DurationRange'
Expand Down Expand Up @@ -53,6 +55,15 @@ const DemographicForm = (props: CriteriaDrawerComponentProps) => {
const [title, setTitle] = useState(selectedCriteria?.title || 'Critère démographique')
const [isInclusive, setIsInclusive] = useState<boolean>(selectedCriteria?.isInclusive || true)

const selectedPopulation = useAppSelector((state) => state.cohortCreation.request.selectedPopulation || [])

const deidentified: boolean | undefined =
selectedPopulation === null
? undefined
: selectedPopulation
.map((population) => population && population.access)
.filter((elem) => elem && elem === 'Pseudonymisé').length > 0

const { classes } = useStyles()

const [error, setError] = useState(Error.NO_ERROR)
Expand Down Expand Up @@ -153,16 +164,18 @@ const DemographicForm = (props: CriteriaDrawerComponentProps) => {
renderInput={(params) => <TextField {...params} label="Statut vital" />}
/>

<BlockWrapper margin="1em">
<CalendarRange
inline
disabled={age[0] !== null || age[1] !== null}
value={birthdates}
label={'Date de naissance'}
onChange={(value) => setBirthdates(value)}
onError={(isError) => setError(isError ? Error.INCOHERENT_AGE_ERROR : Error.NO_ERROR)}
/>
</BlockWrapper>
{!deidentified && (
<BlockWrapper margin="1em">
<CalendarRange
inline
disabled={age[0] !== null || age[1] !== null}
value={birthdates}
label={'Date de naissance'}
onChange={(value) => setBirthdates(value)}
onError={(isError) => setError(isError ? Error.INCOHERENT_AGE_ERROR : Error.NO_ERROR)}
/>
</BlockWrapper>
)}

<BlockWrapper margin="1em">
<DurationRange
Expand All @@ -177,9 +190,11 @@ const DemographicForm = (props: CriteriaDrawerComponentProps) => {
}
onChange={(value) => setAge(value)}
onError={(isError) => setError(isError ? Error.INCOHERENT_AGE_ERROR : Error.NO_ERROR)}
deidentified={deidentified}
/>
</BlockWrapper>
{vitalStatus &&
{!deidentified &&
vitalStatus &&
(vitalStatus.length === 0 ||
(vitalStatus.length === 1 &&
vitalStatus.find((status: LabelObject) => status.label === VitalStatusLabel.DECEASED))) && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
import InfoIcon from '@mui/icons-material/Info'

import useStyles from './styles'
import { useAppSelector } from 'state'

import { CriteriaDrawerComponentProps, CriteriaName, ScopeTreeRow } from 'types'
import PopulationCard from '../../../../PopulationCard/PopulationCard'
Expand Down Expand Up @@ -99,6 +100,14 @@ const EncounterForm = ({
const [multiFields, setMultiFields] = useState<string | null>(localStorage.getItem('multiple_fields'))
const isEdition = selectedCriteria !== null ? true : false
const [error, setError] = useState(Error.NO_ERROR)
const selectedPopulation = useAppSelector((state) => state.cohortCreation.request.selectedPopulation || [])

const deidentified: boolean | undefined =
selectedPopulation === null
? undefined
: selectedPopulation
.map((population) => population && population.access)
.filter((elem) => elem && elem === 'Pseudonymisé').length > 0

useEffect(() => {
setError(Error.NO_ERROR)
Expand Down Expand Up @@ -246,6 +255,7 @@ const EncounterForm = ({
value={age}
onChange={(value) => setAge(value)}
onError={(isError) => setError(isError ? Error.INCOHERENT_AGE_ERROR : Error.NO_ERROR)}
deidentified={deidentified}
/>
</BlockWrapper>

Expand Down
10 changes: 5 additions & 5 deletions src/utils/age.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,13 @@ export const convertDurationToTimestamp = (duration: DurationType | null): numbe
return year * 365 + month * 30 + day
}

export const convertTimestampToDuration = (timestamp: number | null): DurationType => {
export const convertTimestampToDuration = (timestamp: number | null, type: Calendar): DurationType => {
const duration: DurationType = { year: 130, month: 0, day: 0 }
if (!timestamp) return duration
duration.year = Math.floor(timestamp / 365)
timestamp = timestamp % 365
duration.month = Math.floor(timestamp / 30)
timestamp = timestamp % 30
duration.year = type === Calendar.MONTH ? Math.floor(timestamp / 12) : Math.floor(timestamp / 365)
timestamp = type === Calendar.MONTH ? timestamp % 12 : timestamp % 365
duration.month = type === Calendar.MONTH ? Math.floor(timestamp / 1) : Math.floor(timestamp / 30)
timestamp = type === Calendar.MONTH ? timestamp % 1 : timestamp % 30
duration.day = timestamp
return duration
}
86 changes: 57 additions & 29 deletions src/utils/cohortCreation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import {
convertDurationToString,
convertDurationToTimestamp,
convertStringToDuration,
convertTimestampToDuration
convertTimestampToDuration,
substructAgeString
} from './age'
import { Comparators, DocType, RessourceType, SelectedCriteriaType, CriteriaDataKey } from 'types/requestCriterias'
import { comparatorToFilter, parseOccurence } from './valueComparator'
Expand All @@ -36,7 +37,8 @@ const IPP_LIST_FHIR = 'identifier.value'

const PATIENT_GENDER = 'gender'
const PATIENT_BIRTHDATE = 'birthdate'
const PATIENT_AGE = 'age-day'
const PATIENT_AGE_DAY = 'age-day'
const PATIENT_AGE_MONTH = 'age-month'
const PATIENT_DEATHDATE = 'death-date'
const PATIENT_DECEASED = 'deceased'

Expand Down Expand Up @@ -191,7 +193,7 @@ export const getCalendarMultiplicator = (type: Calendar): number => {
}
}

const constructFilterFhir = (criterion: SelectedCriteriaType): string => {
const constructFilterFhir = (criterion: SelectedCriteriaType, deidentified: boolean): string => {
let filterFhir = ''
const filterReducer = (accumulator: any, currentValue: any): string =>
accumulator ? `${accumulator}&${currentValue}` : currentValue ? currentValue : accumulator
Expand All @@ -200,15 +202,16 @@ const constructFilterFhir = (criterion: SelectedCriteriaType): string => {

switch (criterion.type) {
case RessourceType.PATIENT: {
const ageMin = convertDurationToTimestamp(
convertStringToDuration(criterion.age?.[0]) || { year: 0, month: 0, day: 0 }
)
const ageMax = convertDurationToTimestamp(
convertStringToDuration(criterion.age?.[1]) || { year: 130, month: 0, day: 0 }
)
const birthdates: [string, string] = [
moment(substructAgeString(criterion?.age?.[0] || '0/0/0')).format('MM/DD/YYYY'),
moment(substructAgeString(criterion?.age?.[1] || '0/0/130')).format('MM/DD/YYYY')
]

const ageMinCriterion = `${PATIENT_AGE}=ge${ageMin}`
const ageMaxCriterion = `${PATIENT_AGE}=le${ageMax}`
const ageMin = Math.abs(moment(birthdates[0]).diff(moment(), deidentified ? 'months' : 'days'))
const ageMax = Math.abs(moment(birthdates[1]).diff(moment(), deidentified ? 'months' : 'days'))

const ageMinCriterion = `${deidentified ? PATIENT_AGE_MONTH : PATIENT_AGE_DAY}=ge${ageMin}`
const ageMaxCriterion = `${deidentified ? PATIENT_AGE_MONTH : PATIENT_AGE_DAY}=le${ageMax}`

filterFhir = [
'active=true',
Expand Down Expand Up @@ -243,6 +246,19 @@ const constructFilterFhir = (criterion: SelectedCriteriaType): string => {
}

case RessourceType.ENCOUNTER: {
const birthdates: [string, string] = [
moment(substructAgeString(criterion?.age?.[0] || '0/0/0')).format('MM/DD/YYYY'),
moment(substructAgeString(criterion?.age?.[1] || '0/0/130')).format('MM/DD/YYYY')
]

deidentified = false //TODO erase this line when deidentified param for encounter is implemented

const ageMin = Math.abs(moment(birthdates[0]).diff(moment(), deidentified ? 'months' : 'days'))
const ageMax = Math.abs(moment(birthdates[1]).diff(moment(), deidentified ? 'months' : 'days'))

const ageMinCriterion = `${deidentified ? ENCOUNTER_MIN_BIRTHDATE : ENCOUNTER_MIN_BIRTHDATE}=ge${ageMin}`
const ageMaxCriterion = `${deidentified ? ENCOUNTER_MAX_BIRTHDATE : ENCOUNTER_MAX_BIRTHDATE}=le${ageMax}`

filterFhir = [
'subject.active=true',
`${
Expand Down Expand Up @@ -320,16 +336,8 @@ const constructFilterFhir = (criterion: SelectedCriteriaType): string => {
? `${ENCOUNTER_DURATION}=le${convertDurationToTimestamp(convertStringToDuration(criterion.duration?.[1]))}`
: ''
}`,
`${
criterion.age?.[0]
? `${ENCOUNTER_MIN_BIRTHDATE}=ge${convertDurationToTimestamp(convertStringToDuration(criterion.age?.[0]))}`
: ''
}`,
`${
criterion.age?.[1]
? `${ENCOUNTER_MAX_BIRTHDATE}=le${convertDurationToTimestamp(convertStringToDuration(criterion.age?.[1]))}`
: ''
}`
`${criterion.age?.[0] ? ageMinCriterion : ''}`,
`${criterion.age?.[1] ? ageMaxCriterion : ''}`
]
.filter((elem) => elem)
.reduce(filterReducer)
Expand Down Expand Up @@ -661,6 +669,12 @@ export function buildRequest(
): string {
if (!selectedPopulation) return ''
selectedPopulation = selectedPopulation.filter((elem) => elem !== undefined)
const deidentified: boolean =
selectedPopulation === null
? false
: selectedPopulation
.map((population) => population && population.access)
.filter((elem) => elem && elem === 'Pseudonymisé').length > 0

const exploreCriteriaGroup = (itemIds: number[]): (RequeteurCriteriaType | RequeteurGroupType)[] => {
let children: (RequeteurCriteriaType | RequeteurGroupType)[] = []
Expand All @@ -677,7 +691,7 @@ export function buildRequest(
_id: item.id ?? 0,
isInclusive: item.isInclusive ?? true,
resourceType: item.type ?? RessourceType.PATIENT,
filterFhir: constructFilterFhir(item),
filterFhir: constructFilterFhir(item, deidentified),
occurrence:
!(item.type === RessourceType.PATIENT || item.type === RessourceType.IPP_LIST) && item.occurrence
? {
Expand Down Expand Up @@ -858,13 +872,23 @@ export async function unbuildRequest(_json: string): Promise<any> {
const value = filter ? filter[1] : null

switch (key) {
case PATIENT_AGE: {
case PATIENT_AGE_DAY: {
if (value?.includes('ge')) {
const ageMin = value?.replace('ge', '')
currentCriterion.age[0] = convertDurationToString(convertTimestampToDuration(+ageMin, Calendar.DAY))
} else if (value?.includes('le')) {
const ageMax = value?.replace('le', '')
currentCriterion.age[1] = convertDurationToString(convertTimestampToDuration(+ageMax, Calendar.DAY))
}
break
}
case PATIENT_AGE_MONTH: {
if (value?.includes('ge')) {
const ageMin = value?.replace('ge', '')
currentCriterion.age[0] = convertDurationToString(convertTimestampToDuration(+ageMin))
currentCriterion.age[0] = convertDurationToString(convertTimestampToDuration(+ageMin, Calendar.MONTH))
} else if (value?.includes('le')) {
const ageMax = value?.replace('le', '')
currentCriterion.age[1] = convertDurationToString(convertTimestampToDuration(+ageMax))
currentCriterion.age[1] = convertDurationToString(convertTimestampToDuration(+ageMax, Calendar.MONTH))
}
break
}
Expand Down Expand Up @@ -952,21 +976,25 @@ export async function unbuildRequest(_json: string): Promise<any> {
case ENCOUNTER_DURATION: {
if (value.includes('ge')) {
const durationMin = value?.replace('ge', '')
currentCriterion.duration[0] = convertDurationToString(convertTimestampToDuration(+durationMin))
currentCriterion.duration[0] = convertDurationToString(
convertTimestampToDuration(+durationMin, Calendar.DAY)
)
} else if (value.includes('le')) {
const durationMax = value?.replace('le', '')
currentCriterion.duration[1] = convertDurationToString(convertTimestampToDuration(+durationMax))
currentCriterion.duration[1] = convertDurationToString(
convertTimestampToDuration(+durationMax, Calendar.DAY)
)
}
break
}
case ENCOUNTER_MIN_BIRTHDATE: {
const ageMin = value?.replace('ge', '')
currentCriterion.age[0] = convertDurationToString(convertTimestampToDuration(+ageMin))
currentCriterion.age[0] = convertDurationToString(convertTimestampToDuration(+ageMin, Calendar.DAY))
break
}
case ENCOUNTER_MAX_BIRTHDATE: {
const ageMax = value?.replace('le', '')
currentCriterion.age[1] = convertDurationToString(convertTimestampToDuration(+ageMax))
currentCriterion.age[1] = convertDurationToString(convertTimestampToDuration(+ageMax, Calendar.DAY))
break
}
case ENCOUNTER_ENTRYMODE: {
Expand Down

0 comments on commit 5bb121d

Please sign in to comment.