OHDSI / Capr

Cohort definition Application Programming in R
https://ohdsi.github.io/Capr
Apache License 2.0
17 stars 11 forks source link

Support updates to Circe v1.11.0 #77

Closed chrisknoll closed 1 year ago

chrisknoll commented 1 year ago

Circe 1.11.0 adds a new feature to date-offset start/end dates of 'query' criteria. This allows you to override an end date of an index event, or re-assign a start date to some other time. Example: A pregnancy confirmation event may indicate 12-weeks, so you may want to offset the start date of this event to indicate a pregnancy start by 7*12=84d.

The main change of the of the API is that all Criteria now have a DateAdjustment field, used here.

The DateAdjustment class is described here.

ablack3 commented 1 year ago

thanks for the heads up @chrisknoll

mdlavallee92 commented 1 year ago

@chrisknoll making sure I understand this change correctly...

the DateAdjustment class is a type of attribute correct? So a conditionOccurrence query can have a DateAdjustment attribute. That being said this would not mess with the query object, but I could add this attribute to the query. As long as it is not messing with the Query class then this would be an enhancement and we can implement without causing major changes to the circe coercion.

So is the interface is suppose to look like this?

t1dConceptSet <- cs(descendants(195771), name = "T1D") 

offsetT1d <- conditionOccurrence(t1dConceptSet,  
                             dateAdjustment(startWith = "start", startDays = 30L, endsWith = "end", endDays = 30L) 

Another question: which domains can the DateAdjustment attribute be enabled?

chrisknoll commented 1 year ago

DateAdjustment is a type of attribute, and the name of the attribute is 'DateAdjustment', so there could be a little bit of confusion there, but the attribute 'DateAdjustment' of Criteria is of type 'DateAdjustment'. We have a convention that attribute names are Capitalized, hence the confusion there. But I think you got the idea.

This attribute is done at a 'base class' level of Criteria: These are the subtypes:

@JsonSubTypes({
  @JsonSubTypes.Type(value = ConditionEra.class, name = "ConditionEra"),
  @JsonSubTypes.Type(value = ConditionOccurrence.class, name = "ConditionOccurrence"),
  @JsonSubTypes.Type(value = Death.class, name = "Death"),
  @JsonSubTypes.Type(value = DeviceExposure.class, name = "DeviceExposure"),
  @JsonSubTypes.Type(value = DoseEra.class, name = "DoseEra"),
  @JsonSubTypes.Type(value = DrugEra.class, name = "DrugEra"),
  @JsonSubTypes.Type(value = DrugExposure.class, name = "DrugExposure"),
  @JsonSubTypes.Type(value = LocationRegion.class, name = "LocationRegion"),
  @JsonSubTypes.Type(value = Measurement.class, name = "Measurement"),
  @JsonSubTypes.Type(value = Observation.class, name = "Observation"),
  @JsonSubTypes.Type(value = ObservationPeriod.class, name = "ObservationPeriod"),
  @JsonSubTypes.Type(value = ProcedureOccurrence.class, name = "ProcedureOccurrence"),
  @JsonSubTypes.Type(value = Specimen.class, name = "Specimen"),
  @JsonSubTypes.Type(value = VisitOccurrence.class, name = "VisitOccurrence"),
  @JsonSubTypes.Type(value = VisitDetail.class, name = "VisitDetail"),
  @JsonSubTypes.Type(value = PayerPlanPeriod.class, name = "PayerPlanPeriod")
})

For your example, you're bulding the object graph a little differently than I would expect using an object model ie:

conditionOccurrence <-  ConditionOccurrence$new()
conditionOccurrence$CodesetId<- t1dConceptSet$id
dateAdjustment <- DateAdjustment$new()
dateAdjustment$StartWith <- "START_DATE"
dateAdjustment$StartOffset <- 30L
dateAdjustment$EndWith <- "END_DATE"
dateAdjustment$EndOffset <- 30L
conditionOccurrence$DateAdjustment <- dateAdjustment 

If your conditionOccurrence() function is assigning those function params to the object model, then you'd just translate startsWith to the StartWith JSON field and the value of start to `START_DATE'.

On my branch, I built a criteria ConditionOccurrence with the exported JSON as:

      {
        "ConditionOccurrence": {
          "DateAdjustment": {
            "StartWith": "START_DATE",
            "StartOffset": 30,
            "EndWith": "END_DATE",
            "EndOffset": 30
          }
        }
      }

If you produce that from your example, then you're making the correct JSON.

If you are checking the SQL result, then the SQL that is created to adjust dates is in this part:

-- Begin Condition Occurrence Criteria
SELECT C.person_id, C.condition_occurrence_id as event_id, C.start_date, C.end_date, C.visit_occurrence_id, C.start_date as sort_date
FROM 
(
  SELECT co.person_id,co.condition_occurrence_id,co.condition_concept_id,co.visit_occurrence_id,DATEADD(day,30,co.condition_start_date) as start_date, DATEADD(day,30,COALESCE(co.condition_end_date, DATEADD(day,1,co.condition_start_date))) as end_date 
  FROM @cdm_database_schema.CONDITION_OCCURRENCE co

) C

WHERE C.end_date >= C.start_date
-- End Condition Occurrence Criteria

Note: the COALESCE here is handling if you have a NULL condition_end_date, it will use 1 day after the condition_start_date as the event's end date.

If you're wondering why the ugly enum values for 'START_DATE' and 'END_DATE', I was wondering about that myself (why, Chris, didn't you just use 'start' and 'end'?) But, It's already published with this, so it feels ugly, but it's released so I am just going to hold my nose when looking at it.

mdlavallee92 commented 1 year ago

Ok great, I thought I was on the right track just wanted to confirm. I want to discuss your object graph a bit but will make that as a separate issue.

mdlavallee92 commented 1 year ago

@chrisknoll, dateAdjustment it is now in v2.0.6. Added the functionality to Capr and got no errors feeding the json into CirceR version 1.3.1. See below:

library(Capr)

#make concept set for celecoxib
celecoxib <- cs(descendants(1118084), name = "celecoxib")

#make cohort for celecoxib
celecoxibCohort <- cohort(
  entry = entry(
    drugExposure(celecoxib,
                 dateAdjustment(startOffset = 30L, endOffset = 30L))
  ),
  exit = exit(
    observationExit()
  )
)

jj <- compile(celecoxibCohort, pretty = TRUE)

cat(jj)
#> {
#>   "ConceptSets": [
#>     {
#>       "id": 0,
#>       "name": "celecoxib",
#>       "expression": {
#>         "items": [
#>           {
#>             "concept": {
#>               "CONCEPT_ID": 1118084,
#>               "CONCEPT_NAME": "",
#>               "STANDARD_CONCEPT": "",
#>               "STANDARD_CONCEPT_CAPTION": "",
#>               "INVALID_REASON": "",
#>               "INVALID_REASON_CAPTION": "",
#>               "CONCEPT_CODE": "",
#>               "DOMAIN_ID": "",
#>               "VOCABULARY_ID": "",
#>               "CONCEPT_CLASS_ID": ""
#>             },
#>             "isExcluded": false,
#>             "includeDescendants": true,
#>             "includeMapped": false
#>           }
#>         ]
#>       }
#>     }
#>   ],
#>   "PrimaryCriteria": {
#>     "CriteriaList": [
#>       {
#>         "DrugExposure": {
#>           "CodesetId": 0,
#>           "DateAdjustment": {
#>             "StartWith": "START_DATE",
#>             "StartOffset": 30,
#>             "EndWith": "END_DATE",
#>             "EndOffset": 30
#>           }
#>         }
#>       }
#>     ],
#>     "ObservationWindow": {
#>       "PriorDays": 0,
#>       "PostDays": 0
#>     },
#>     "PrimaryCriteriaLimit": {
#>       "Type": "First"
#>     }
#>   },
#>   "QualifiedLimit": {
#>     "Type": "First"
#>   },
#>   "ExpressionLimit": {
#>     "Type": "First"
#>   },
#>   "InclusionRules": [],
#>   "CensoringCriteria": [],
#>   "CollapseSettings": {
#>     "CollapseType": "ERA",
#>     "EraPad": 0
#>   },
#>   "CensorWindow": {},
#>   "cdmVersionRange": ">=5.0.0"
#> }

tst <- CirceR::buildCohortQuery(
  expression = jj,
  options = CirceR::createGenerateOptions(generateStats = TRUE)
)
cat(tst)
#> CREATE TABLE #Codesets (
#>   codeset_id int NOT NULL,
#>   concept_id bigint NOT NULL
#> )
#> ;
#> 
#> INSERT INTO #Codesets (codeset_id, concept_id)
#> SELECT 0 as codeset_id, c.concept_id FROM (select distinct I.concept_id FROM
#> ( 
#>   select concept_id from @vocabulary_database_schema.CONCEPT where concept_id in (1118084)
#> UNION  select c.concept_id
#>   from @vocabulary_database_schema.CONCEPT c
#>   join @vocabulary_database_schema.CONCEPT_ANCESTOR ca on c.concept_id = ca.descendant_concept_id
#>   and ca.ancestor_concept_id in (1118084)
#>   and c.invalid_reason is null
#> 
#> ) I
#> ) C;
#> 
#> UPDATE STATISTICS #Codesets;
#> 
#> 
#> SELECT event_id, person_id, start_date, end_date, op_start_date, op_end_date, visit_occurrence_id
#> INTO #qualified_events
#> FROM 
#> (
#>   select pe.event_id, pe.person_id, pe.start_date, pe.end_date, pe.op_start_date, pe.op_end_date, row_number() over (partition by pe.person_id order by pe.start_date ASC) as ordinal, cast(pe.visit_occurrence_id as bigint) as visit_occurrence_id
#>   FROM (-- Begin Primary Events
#> select P.ordinal as event_id, P.person_id, P.start_date, P.end_date, op_start_date, op_end_date, cast(P.visit_occurrence_id as bigint) as visit_occurrence_id
#> FROM
#> (
#>   select E.person_id, E.start_date, E.end_date,
#>          row_number() OVER (PARTITION BY E.person_id ORDER BY E.sort_date ASC, E.event_id) ordinal,
#>          OP.observation_period_start_date as op_start_date, OP.observation_period_end_date as op_end_date, cast(E.visit_occurrence_id as bigint) as visit_occurrence_id
#>   FROM 
#>   (
#>   -- Begin Drug Exposure Criteria
#> select C.person_id, C.drug_exposure_id as event_id, C.start_date, C.end_date,
#>   C.visit_occurrence_id,C.start_date as sort_date
#> from 
#> (
#>   select de.person_id,de.drug_exposure_id,de.drug_concept_id,de.visit_occurrence_id,days_supply,quantity,refills,DATEADD(day,30,de.drug_exposure_start_date) as start_date, DATEADD(day,30,COALESCE(de.drug_exposure_end_date, DATEADD(day,de.days_supply,de.drug_exposure_start_date), DATEADD(day,1,de.drug_exposure_start_date))) as end_date 
#>   FROM @cdm_database_schema.DRUG_EXPOSURE de
#> JOIN #Codesets cs on (de.drug_concept_id = cs.concept_id and cs.codeset_id = 0)
#> ) C
#> 
#> WHERE C.end_date >= C.start_date
#> -- End Drug Exposure Criteria
#> ...truncate....

Created on 2023-09-07 with reprex v2.0.2

Closing...