From bc7579a796d2c32e9d57047024f668766c0dd18d Mon Sep 17 00:00:00 2001 From: Kyle Zollo-Venecek Date: Wed, 10 Jan 2024 10:26:38 -0500 Subject: [PATCH 01/10] Add oncology validation package --- validationScripts/OncologyValidation.R | 265 ++++++++++++++++++ .../OncologyValidationScripts.Rproj | 13 + validationScripts/README.md | 176 ++++++++++++ validationScripts/dateOfInitialDiagnosis.md | 144 ++++++++++ .../onc_analysis_details_DONTUSE.csv | 10 + .../achilles_style_dontuse/sql_server/1.sql | 24 ++ .../achilles_style_dontuse/sql_server/10.sql | 8 + .../achilles_style_dontuse/sql_server/11.sql | 8 + .../achilles_style_dontuse/sql_server/12.sql | 9 + .../achilles_style_dontuse/sql_server/13.sql | 18 ++ .../achilles_style_dontuse/sql_server/14.sql | 19 ++ .../achilles_style_dontuse/sql_server/15.sql | 10 + .../achilles_style_dontuse/sql_server/16.sql | 46 +++ .../achilles_style_dontuse/sql_server/2.sql | 24 ++ .../achilles_style_dontuse/sql_server/3.sql | 26 ++ .../inst/csv/onc_analysis_queries.csv | 9 + validationScripts/inst/description.MD | 12 + .../inst/json/onc_analysis_composite.json | 74 +++++ .../inst/sql/composite_analyses/1.sql | 6 + .../inst/sql/composite_analyses/10.sql | 7 + .../inst/sql/composite_analyses/1001.sql | 1 + .../inst/sql/composite_analyses/1002.sql | 1 + .../inst/sql/composite_analyses/11.sql | 7 + .../inst/sql/composite_analyses/12.sql | 46 +++ .../inst/sql/composite_analyses/13.sql | 16 ++ .../inst/sql/composite_analyses/14.sql | 1 + .../inst/sql/composite_analyses/15.sql | 9 + .../inst/sql/composite_analyses/1old.sql | 23 ++ .../inst/sql/composite_analyses/2.sql | 21 ++ .../sql/composite_analyses/analysisInsert.sql | 4 + .../inst/sql/onc_validation_analysis_ddl.sql | 16 ++ .../inst/sql/onc_validation_results_ddl.sql | 14 + validationScripts/inst/sql/queries/1.sql | 21 ++ validationScripts/inst/sql/queries/10.sql | 5 + validationScripts/inst/sql/queries/11.sql | 5 + validationScripts/inst/sql/queries/12.sql | 6 + validationScripts/inst/sql/queries/13.sql | 15 + validationScripts/inst/sql/queries/14.sql | 16 ++ validationScripts/inst/sql/queries/15.sql | 9 + validationScripts/inst/sql/queries/2.sql | 21 ++ .../inst/sql/queries/basicInsert.sql | 7 + .../inst/sql/spark/readQueryTables.sql | 1 + 42 files changed, 1173 insertions(+) create mode 100644 validationScripts/OncologyValidation.R create mode 100644 validationScripts/OncologyValidationScripts.Rproj create mode 100644 validationScripts/README.md create mode 100644 validationScripts/dateOfInitialDiagnosis.md create mode 100644 validationScripts/inst/achilles_style_dontuse/onc_analysis_details_DONTUSE.csv create mode 100644 validationScripts/inst/achilles_style_dontuse/sql_server/1.sql create mode 100644 validationScripts/inst/achilles_style_dontuse/sql_server/10.sql create mode 100644 validationScripts/inst/achilles_style_dontuse/sql_server/11.sql create mode 100644 validationScripts/inst/achilles_style_dontuse/sql_server/12.sql create mode 100644 validationScripts/inst/achilles_style_dontuse/sql_server/13.sql create mode 100644 validationScripts/inst/achilles_style_dontuse/sql_server/14.sql create mode 100644 validationScripts/inst/achilles_style_dontuse/sql_server/15.sql create mode 100644 validationScripts/inst/achilles_style_dontuse/sql_server/16.sql create mode 100644 validationScripts/inst/achilles_style_dontuse/sql_server/2.sql create mode 100644 validationScripts/inst/achilles_style_dontuse/sql_server/3.sql create mode 100644 validationScripts/inst/csv/onc_analysis_queries.csv create mode 100644 validationScripts/inst/description.MD create mode 100644 validationScripts/inst/json/onc_analysis_composite.json create mode 100644 validationScripts/inst/sql/composite_analyses/1.sql create mode 100644 validationScripts/inst/sql/composite_analyses/10.sql create mode 100644 validationScripts/inst/sql/composite_analyses/1001.sql create mode 100644 validationScripts/inst/sql/composite_analyses/1002.sql create mode 100644 validationScripts/inst/sql/composite_analyses/11.sql create mode 100644 validationScripts/inst/sql/composite_analyses/12.sql create mode 100644 validationScripts/inst/sql/composite_analyses/13.sql create mode 100644 validationScripts/inst/sql/composite_analyses/14.sql create mode 100644 validationScripts/inst/sql/composite_analyses/15.sql create mode 100644 validationScripts/inst/sql/composite_analyses/1old.sql create mode 100644 validationScripts/inst/sql/composite_analyses/2.sql create mode 100644 validationScripts/inst/sql/composite_analyses/analysisInsert.sql create mode 100644 validationScripts/inst/sql/onc_validation_analysis_ddl.sql create mode 100644 validationScripts/inst/sql/onc_validation_results_ddl.sql create mode 100644 validationScripts/inst/sql/queries/1.sql create mode 100644 validationScripts/inst/sql/queries/10.sql create mode 100644 validationScripts/inst/sql/queries/11.sql create mode 100644 validationScripts/inst/sql/queries/12.sql create mode 100644 validationScripts/inst/sql/queries/13.sql create mode 100644 validationScripts/inst/sql/queries/14.sql create mode 100644 validationScripts/inst/sql/queries/15.sql create mode 100644 validationScripts/inst/sql/queries/2.sql create mode 100644 validationScripts/inst/sql/queries/basicInsert.sql create mode 100644 validationScripts/inst/sql/spark/readQueryTables.sql diff --git a/validationScripts/OncologyValidation.R b/validationScripts/OncologyValidation.R new file mode 100644 index 0000000..36b0f0b --- /dev/null +++ b/validationScripts/OncologyValidation.R @@ -0,0 +1,265 @@ +library(SqlRender) +library(jsonlite) + + +# Helpers ----------------------------------------------------------------- + +# TODO change everything about this to "OncAnalysisDetails" +#' Get Oncology Validation query details +#' +#' @return +#' @export +#' +#' @examples +getOncAnalysisQueries <- function() { + read.csv(file.path('./inst/csv/onc_analysis_queries.csv')) +} + + +#' Get the analysis text from a composite analysis file +#' +#' @param analysisNumber +#' +#' @return (string) A SQL script as a string +#' + +getAnalysisText <- function(analysisNumber) { + fp <- file.path('.', 'inst', 'sql', 'composite_analyses', paste0(analysisNumber, '.sql')) + readChar(fp, file.info(fp)$size) +} + + +#' Get the query text from a base query file +#' +#' @param queryNumber +#' +#' @return (string) A SQL script as a string +#' + +getQueryText <- function(queryNumber) { + fp <- file.path('.', 'inst', 'sql', 'queries', paste0(queryNumber, '.sql')) + readChar(fp, file.info(fp)$size) +} + +#' Title +#' +#' @param connectionDetails +#' @param scratchDatabaseSchema +#' +#' @return +#' @export +#' +#' @examples +getExistingQueryNumbers <- function(connectionDetails, scratchDatabaseSchema) { + # REad table names from scratchDatabase + # There is not way to translate between these operations, so will need one for each supported database + + sql <- render('SHOW TABLES FROM @scratchDatabaseSchema LIKE \'onc_val_*\'', + scratchDatabaseSchema = scratchDatabaseSchema) + conn <- DatabaseConnector::connect(connectionDetails) + on.exit(DatabaseConnector::disconnect(conn)) + queryTables <- DatabaseConnector::querySql(conn, sql = sql)$TABLENAME + as.numeric(gsub(".*_([0-9]+)$", "\\1", queryTables)) +} + + +# DDL Handlers ------------------------------------------------------------ + +# Create composite analysis table +#' +#' @param connectionDetails +#' @param resultsDatabaseSchema +#' @param createTable +#' +#' @return +#' @export +#' +#' @examples +#' + +createAnalysisTable <- function(connectionDetails, resultsDatabaseSchema, createTable = TRUE) { + if (isTRUE(createTable)) { + fp <- file.path('.', 'inst', 'sql', 'onc_validation_analysis_ddl.sql') + sql <- readChar(fp, file.info(fp)$size) + rendered <- render(sql, resultsDatabaseSchema = resultsDatabaseSchema) + renderedTranslated <- translate(rendered, targetDialect = connectionDetails$dbms) + conn <- DatabaseConnector::connect(connectionDetails) + on.exit(DatabaseConnector::disconnect(conn)) + DatabaseConnector::executeSql(conn, sql = renderedTranslated) + } +} + + +#' Create composite analysis results table +#' +#' @param connectionDetails +#' @param resultsDatabaseSchema +#' @param overwrite +#' +#' @return +#' @export +#' +#' @examples +#' + +createResultsTable <- function(connectionDetails, resultsDatabaseSchema, overwrite = FALSE) { + fp <- file.path('.', 'inst', 'sql', 'onc_validation_results_ddl.sql') + sql <- readChar(fp, file.info(fp)$size) + rendered <- render(sql, resultsDatabaseSchema = resultsDatabaseSchema) + renderedTranslated <- translate(rendered, targetDialect = connectionDetails$dbms) + conn <- DatabaseConnector::connect(connectionDetails) + on.exit(DatabaseConnector::disconnect(conn)) + exists = DatabaseConnector::existsTable(conn, databaseSchema = resultsDatabaseSchema, tableName = 'onc_validation_results') + if (!overwrite && exists) { + message("Onc Validation results exists. Set overwrite to TRUE to rebuild.") + return(0) + } + DatabaseConnector::executeSql(conn, sql = renderedTranslated) +} + + +# Execution Handlers ------------------------------------------------------ + +executeAnalysis <- function(connectionDetails, + analysisNumber, + cdmDatabaseSchema = cdmDatabaseSchema, + vocabDatabaseSchema = vocabDatabaseSchema, + scratchDatabaseSchema = scratchDatabaseSchema, + resultsDatabaseSchema = resultsDatabaseSchema) { + + composites_path <- file.path('./inst/json/onc_analysis_composite.json') + + composites <- fromJSON(composites_path)$composite_analyses + + requisiteQueryNumbers <- composites$queries[composites['analysis_id'] == analysisNumber][[1]] + + existingQueryNumbers <- getExistingQueryNumbers(connectionDetails, scratchDatabaseSchema) + + missingQueries <- which(!requisiteQueryNumbers %in% existingQueryNumbers) + + if (length(missingQueries)) { + message("Executing required queries") + lapply(missingQueries, function(x) { + executeQuery(connectionDetails, + queryNumber = x, + cdmDatabaseSchema = cdmDatabaseSchema, + vocabDatabaseSchema = vocabDatabaseSchema, + scratchDatabaseSchema = scratchDatabaseSchema) + }) + } + message("All requisite queries executed.") + + + renderedAnalysisText <- render(getAnalysisText(analysisNumber), + scratchDatabaseSchema = scratchDatabaseSchema) + + renderedInsertAnalysisText <- render(getAnalysisText('analysisInsert'), + insertSchema = scratchDatabaseSchema, + analysisText = renderedAnalysisText) + + translatedRenderedInsertAnalysisText <- translate(renderedInsertAnalysisText, targetDialect = connectionDetails$dbms) + + message(paste0("Executing analysis ", analysisNumber, ", writing to ", resultsDatabaseSchema, ".onc_validation_results")) + conn <- DatabaseConnector::connect(connectionDetails) + on.exit(DatabaseConnector::disconnect(conn)) + DatabaseConnector::executeSql(conn, sql = translatedRenderedInsertAnalysisText) + analysisNumber +} + + +#' Title +#' +#' @param connectionDetails +#' @param queryNumber +#' @param cdmDatabaseSchema +#' @param vocabDatabaseSchema +#' @param scratchDatabaseSchema +#' +#' @return +#' + +executeQuery <- function(connectionDetails, + queryNumber, + cdmDatabaseSchema = cdmDatabaseSchema, + vocabDatabaseSchema = vocabDatabaseSchema, + scratchDatabaseSchema = scratchDatabaseSchema) { + + queries <- getOncAnalysisQueries() + + queryTableName <- paste("onc_val", queries$category[queries['granular_analysis_id'] == queryNumber], queryNumber, sep ='_') + + + renderedQueryText <- render(getQueryText(queryNumber), + cdmDatabaseSchema = cdmDatabaseSchema, + vocabDatabaseSchema = vocabDatabaseSchema) + + renderedInsertQueryText <- render(getQueryText('basicInsert'), + insertSchema = scratchDatabaseSchema, + queryTableName = queryTableName, + queryText = renderedQueryText) + translatedRenderedInsertQueryText <- translate(renderedInsertQueryText, targetDialect = connectionDetails$dbms) + + message(paste0("Executing query ", queryNumber, ", writing to ", scratchDatabaseSchema, ".", queryTableName)) + conn <- DatabaseConnector::connect(connectionDetails) + on.exit(DatabaseConnector::disconnect(conn)) + DatabaseConnector::executeSql(conn, sql = translatedRenderedInsertQueryText) + queryTableName +} + + + + + +# Example execution ------------------------------------------------------- + + +connectionDetails <- DatabaseConnector::createConnectionDetails( + dbms = 'spark', + connectionString = keyring::key_get("databricks-connection-string"), + pathToDriver = "C:\\R\\" +) +# @cdmDatabseSchema +# @vocabDatabaseSchema + +cdmDatabaseSchema <- vocabDatabaseSchema <- 'ctsi.trdwlegacyred' + +# @scratchDatabaseSchema +scratchDatabaseSchema <- resultsDatabaseSchema <- 'ctsi.kzollo' + + + +# TODO switch to rebuild all queries... does this go in the analysis section or the query? + + + +createAnalysisTable(connectionDetails, resultsDatabaseSchema) + +createResultsTable(connectionDetails, resultsDatabaseSchema) + + +oncAnalysisQueriesCsv <- getOncAnalysisQueries() +oncAnalysisQueriesCsv <- oncAnalysisQueriesCsv[, -c(2, 3)] + +conn <- DatabaseConnector::connect(connectionDetails) + +# Populate ONC_analysis with data from ONC_ANALYSIS_QUERIES. from above +DatabaseConnector::insertTable( + connection = connection, + databaseSchema = resultsDatabaseSchema, + tableName = "ONC_VALIDATION_ANALYSIS", + data = oncAnalysisQueriesCsv, + dropTableIfExists = FALSE, + createTable = FALSE, + tempTable = FALSE +) + +DatabaseConnector::disconnect(conn) + + +# User asks: How many cancer diagnosis records are in my data? + +executeAnalysis(connectionDetails, analysisNumber = 2, + cdmDatabaseSchema = cdmDatabaseSchema, + vocabDatabaseSchema = vocabDatabaseSchema, + scratchDatabaseSchema = scratchDatabaseSchema, + resultsDatabaseSchema = resultsDatabaseSchema) diff --git a/validationScripts/OncologyValidationScripts.Rproj b/validationScripts/OncologyValidationScripts.Rproj new file mode 100644 index 0000000..8e3c2eb --- /dev/null +++ b/validationScripts/OncologyValidationScripts.Rproj @@ -0,0 +1,13 @@ +Version: 1.0 + +RestoreWorkspace: Default +SaveWorkspace: Default +AlwaysSaveHistory: Default + +EnableCodeIndexing: Yes +UseSpacesForTab: Yes +NumSpacesForTab: 2 +Encoding: UTF-8 + +RnwWeave: Sweave +LaTeX: pdfLaTeX diff --git a/validationScripts/README.md b/validationScripts/README.md new file mode 100644 index 0000000..7cc0dad --- /dev/null +++ b/validationScripts/README.md @@ -0,0 +1,176 @@ +# Oncology Validation Framework + +## Purpose + +Provide a semi-automated and extensible framework for generating, visualizing, and sharing an assessment of an OMOP-shaped database's adherence to the OHDSI Oncology Standard (tables, vocabulary) and the availabilty and types of oncology data it contains. + +## Overview + +The star of the framework is an R Package. Along with cataloguing an extensible set of queries and analyses used for assessing OMOP-shaped oncology data, the R package provides functionality for the four major processes involved in the framework: + +1) Authoring an assessment specification +2) Executing an assessment specification +3) Generating assessment results +4) Visualizing assessment results + +## Analyses + +### Classes of Analyses: + +Analyses can be broken in to ____ different **classes**: + +#### Meta checks + +These analyses are very high-level and run at the very beginning of a validation test. If these tests fail, the user must correct them before the full analysis can be run. + +##### Schema correctness + - Do all relevant oncology-specific tables exist? + - Do all relevant oncology-specific tables have the required column names? + +##### Presence of development vocabulary + - Does the vocabulary table contain the "Oncology development vocabulary" record, signifying that the user has added the Oncology development vocabulary? + - If yes, is the version number the most recent Oncology development vocabulary version? + +#### Existence of Any + +These are analyses that check for the existence of anything unacceptable that must be changed + +##### Incorrect or outdated concepts being used + - Are any terms that have been destandardized by the Oncology development vocabulary being used? + +#### Proportion of cancer patients + +#### Proportion of Task group + +#### Percentage of tests passed for each Task Group + +#### Granularity of data + +## Queries + +Queries are the SQL counterpart to the analyses. Each analysis has a single query with which it is associated. The query lives in a sql file named `.sql` e.g. `1.sql`. + +### Query Classes + +The queries that are used for analyses fall into one of ___ classes: + +#### Count of records by concept sets + - Number of distinct concepts from the "correct" (standard) oncology concept set + - Total number of concepts from the "correct" (standard) oncology concept set + - Number of distinct concepts from the "incorrect" (newly destandardized/ non standard) oncology concept set + - Total number of concepts from the "incorrect" (newly destandardized/ non standard) oncology concept set + +#### Count of patients by concept sets + - How many patients have records associated with the correct/incorrect concept set? + +#### Referential integrity + - Are modifier concepts associated with the correct types of records? E.g. are stage records modifying a cancer condition concept? + + +## Quality checks + +Quality checks are captured as a series of parameterized SQL scripts that + +### Concept sets + +For each "Task Group" for which we are checking quality, we start with the set of standard concepts that may be used for mapping. + +#### Option 1 +These could be compiled in a style similar to the DQD concepts in a CSV: +`cdmTableName,cdmFieldName,conceptId,conceptName...,` + +Where every table, field, and concept group are contained in the same table and are used to parameterize multiple different generic sql scripts. + +A hard-coded list of all correct concepts and all incorrect concepts, in the same place + +#### Option 2 +Alternatively, one list of correct concepts for each "Task Group" could be maintained. To generate the list of incorrect concepts, simply query all non-standard concepts that map to the correct concepts. Along with being much easier to maintain (only need to create a list of correct/standard concepts), this would also allow us to provide suggestions on how to fix incorrect terms... + + + +## List of Analyses: + +| Task Groups | Check Set | +|-------------|-----------| +| Initial Date of Diagnosis | Simple | +| Specific Tumor Identifier | Simple | +| TNM | Full | +| Stage Group | Full | +| Grade | Full | +| Laterality | Full | +| Disease Progression | Full | +| Metastases | Full | +| Dimension | Full | +| Extension/Invasion | Full | +| Radiotherapy | Extended| +| Treatment Intent | Extended| +| Surgery | Extended| +| Drug Therapy | Extended| + + +--- + +| Type of Analysis | Check Set | +| How many records exist | Simple | +| How many records are "correct" | Simple | +| How many records are "incorrect" | Simple | +| How many patients have a record | Simple | + + + + + +TNM Analyses (and Stage Group, Grade, lateratily, disease progression, Metastases, Dimension, Exten/Invsasion) +- How many rows are correct +- How many patients have a correctly mapped TNM record (any record associated is correct) +- How many rows have a foreign key to a condition +- Proportion of rows correct +- Proportion of patients that have a correctly mapped TNM record over total number of patients with cancer diagnosis +- Proportion of rows have a foreign key to a condition +- How many rows are incorrect +- How many patients are incorrect +- How many rows have incorrectly implemented FK (NULL or not to condition) + +Initial Date of Diagnosis (specific tumor identifier) +- How many records exist (characterization) +- How many rows are correct (for in diagn, measurement_event_id points to a valid cancer diagnosis record in condition_occurrence, meas_event_field_concept_id is value 1147127 "condition_occurrence.concidtion_occurence_id") +- How many patients have a date of diagnosis record + + + +Procedures (Radiotherapy, Treatment Intent, Surgery, Drug therapy) + +Radiotherapy example: + +Radiotherapy overview +- if coming from registry: maybe say type, subtype (brachytherapy), total dosage, but not at individual fraction level. Aggregated value of everything they have +- if coming from a stanadrard EHR, will include billing code only (some coarse type-site combo, see CPT codes) +- Oncology specific EMR (Mosaiq) will have everything that registry has but also the individual fraction level (individual "round" of radiotherapy) +- If coming from NLP or flowsheet parsing: as much or similar to onc-specific EMR data + +Procedure Concept sets: +- High level radio therapy concepts +- Billing code radiotherapy concepts +- Low level radio therapy concepts + +Modifier Concept sets +- Topography (correct and incorrect sets) +- Treatment intent +- Fractions (occurrences at individual occurrence level and at aggregated level) + +Radiotherapy analyses + +## How does DQD vs Achilles handle manage analyses + +>TLDR; The checks in DQD are too general for what we are trying to do. Incorporating teh Cancer checks in their current form into DQD would require so much reworking of the DQD framework that it is unlikely the changes would ever get accepted back into a main branch. + + +### DQD + +There are 3 "levels" of checks TABLE FIELD and CONCEPT. The file inst/csv/OMOP_CDMv5.4_Check_Descriptions.csv lists all the checks, their level, name, description, Kahn class, the sqlFile that stores the check, and an "evaluationFilter"(?) + + + +SQL scripts contain the "shells" of the analyses. CSV files contain the parameters for the SQL scripts. + +Ex. concept_plausible_gender.sql takes a schema, table name, field name, concept ID, and the expected gender. The schema is passed from teh R function that executes. The rest are passed from the OMOP_CDMv5.4_Concept_Level.csv file which contains that information in a row that has a value in the column "plausibleGender". \ No newline at end of file diff --git a/validationScripts/dateOfInitialDiagnosis.md b/validationScripts/dateOfInitialDiagnosis.md new file mode 100644 index 0000000..aedbeb8 --- /dev/null +++ b/validationScripts/dateOfInitialDiagnosis.md @@ -0,0 +1,144 @@ +# Date of Initial Diagnosis (validation Script Example) + +## How many records exist + +> Total count of measurement records that have the measurement_concept_id + +``` +SELECT count(*) +FROM ..measurement m +WHERE m.measurement_concept_id = 734306 +``` + +## How many patients have a record + ``` +SELECT count(DISTINCT person_id) +FROM measurement +WHERE measurement_concept_id = 734306 +``` + + + +## How many records are "correct" + +> NOTE: this could be broken down into 3 individual analyses, condensed later. Should it be? + +> To be a correct "date of initial diagnosis record", you need: +> 1. measurement record modifies a record in condition_occurrence (i.e. meas_event_field_concept_id = 1147127) + +``` +-- Number of records not referencing condition_occurrence table +SELECT count(*) +FROM measurement +WHERE measurement_concept_id = 734306 +AND meas_event_field_concept_id <> 1147127 +``` +> 2. each modifier record points to a distinct condition_occurrence record (condition_occurrence.condition_occurrence_id = measurement.measurement_event_id) + +``` +-- Number of condition_occurrence records with more than one "date of initial diagnosis" modifier +-- TODO sum count(condition_occurrence_id) to get number of incorrect modifier records +SELECT count(*) +FROM ( + SELECT count(condition_occurrence_id), condition_occurrence_id + FROM measurement m + INNER JOIN condition_occurrence co + ON m.measurement_event_id = co.condition_occurrence_id + AND measurement_concept_id =734306 + GROUP BY co.condition_occurrence_id +) +WHERE c > 1 +``` +> 3. the condition_occurrence record being modified is a cancer diagnosis as determined by condition_concept_id is descendant concept of 438112 neoplastic disease (should this be restricted to 443392 Malignant neoplastic disease) (Does class need to be 'ICDO Condition'? Of the source_concept_id or the condition_concept_id) + +``` +-- Number of records where modified condition_concept_id is either of class ICDO Condition or is a descendant concept of 438112 "neoplastic disease" +SELECT count(*) +FROM measurement m +INNER JOIN condition_occurrence co +ON m.measurement_event_id = co.condition_occurrence_id +AND measurement_concept_id = 734306 +AND meas_event_field_concept_id <> 1147127 +LEFT JOIN concept c +ON c.concept_id = co.condition_concept_id +LEFT JOIN concept_ancestor ca +ON co.condition_concept_id = ca.descendant_concept_id +AND ca.ancestor_concept_id = 438112 -- neoplastic disease +WHERE (ca.ancestor_concept_id IS NULL AND c.concept_class_id <> 'ICDO Condition') +``` + +> Final/ full logic for "correct" records + +``` +SELECT * +-- SELECT count(DISTINCT measurement_id) +FROM ( + + -- records not referencing condition_occurrence table + SELECT measurement_id, 'wrong table' AS reason + FROM measurement + WHERE measurement_concept_id = 734306 + AND meas_event_field_concept_id <> 1147127 + + UNION ALL + + -- condition_occurrence records with more than one "date of initial diagnosis" modifier + SELECT measurement_id, 'multiple records' AS reason + FROM ( + SELECT count(condition_occurrence_id) as c, condition_occurrence_id + FROM measurement m + INNER JOIN condition_occurrence co + ON m.measurement_event_id = co.condition_occurrence_id + AND measurement_concept_id =734306 + GROUP BY co.condition_occurrence_id + ) co2 + INNER JOIN measurement m2 + ON m2.measurement_event_id = co2.condition_occurrence_id + AND m2.measurement_concept_id =734306 + AND co2.c > 1 + + UNION ALL + + -- modified condition_concept_id is neither of class ICDO Condition nor is a descendant concept of 438112 "neoplastic disease" + SELECT measurement_id, 'wrong record type' AS reason + FROM measurement m + INNER JOIN condition_occurrence co + ON m.measurement_event_id = co.condition_occurrence_id + AND measurement_concept_id = 734306 + AND meas_event_field_concept_id <> 1147127 + LEFT JOIN concept c + ON c.concept_id = co.condition_concept_id + LEFT JOIN concept_ancestor ca + ON co.condition_concept_id = ca.descendant_concept_id + AND ca.ancestor_concept_id = 438112 -- neoplastic disease + WHERE (ca.ancestor_concept_id IS NULL AND c.concept_class_id <> 'ICDO Condition') +) +``` + + +## How many dates are valid +> Date should be after date of birth and before date of death, if exists + +``` +SELECT measurement_id +--SELECT count(measurement_id) +FROM ( + SELECT m.measurement_id + , CASE WHEN p.birth_datetime > m.measurement_datetime THEN 1 + WHEN d.death_datetime < m.measurement_datetime THEN 1 + ELSE 0 END AS invalid_measurement_datetime + FROM measurement m + INNER JOIN person p + ON m.person_id=p.person_id + AND measurement_concept_id = 734306 + LEFT JOIN death d + ON m.person_id=d.person_id +) +WHERE invalid_measurement_datetime > 0 +``` + + + Proportion of correct records (is this derived data point separate?) + How many records are "incorrect" + +## What are the data sources of Date of Initial Diagnosis records \ No newline at end of file diff --git a/validationScripts/inst/achilles_style_dontuse/onc_analysis_details_DONTUSE.csv b/validationScripts/inst/achilles_style_dontuse/onc_analysis_details_DONTUSE.csv new file mode 100644 index 0000000..8f0c0e8 --- /dev/null +++ b/validationScripts/inst/achilles_style_dontuse/onc_analysis_details_DONTUSE.csv @@ -0,0 +1,10 @@ +"analysis_id","distribution","distributed_field","analysis_name","stratum_1_name","stratum_2_name","stratum_3_name","stratum_4_name","stratum_5_name","is_default","category" +"1","0",,"Number of distinct persons with cancer diagnosis",,,,,,"1","General" +"2","0",,"Number of all cancer diagnoses",,,,,,"1","General" +"3","0",,"Number of all cancer diagnoses by condition_concept_id",condition_concept_id,,,,,"1","General" +"10","0",,"Number of date of initial diagnosis modifier records",,,,,,"1","Date of Initial Diagnosis" +"11","0",,"Number of persons with a date of initial diagnosis modifier record",,,,,,"1","Date of Initial Diagnosis" +"12","0",,"Number of date of initial diagnosis modifier records that do not reference the condition_occurrence table",,,,,,"1","Date of Initial Diagnosis" +"13","0",,"Number of date of initial diagnosis modifier records that reference the same condition_occurrence record",,,,,,"1","Date of Initial Diagnosis" +"14","0",,"Number of date of initial diagnosis modifier records that have implausible dates",,,,,,"1","Date of Initial Diagnosis" +"15","0",,"Number of date of initial diagnosis modifier records by measurement_type_concept_id",measurement_type_concept_id,,,,,"1","Date of Initial Diagnosis" diff --git a/validationScripts/inst/achilles_style_dontuse/sql_server/1.sql b/validationScripts/inst/achilles_style_dontuse/sql_server/1.sql new file mode 100644 index 0000000..007097e --- /dev/null +++ b/validationScripts/inst/achilles_style_dontuse/sql_server/1.sql @@ -0,0 +1,24 @@ +-- 1 Number of distinct persons with cancer record + +select 1 as analysis_id, +cast(null as varchar(255)) as stratum_1, cast(null as varchar(255)) as stratum_2, cast(null as varchar(255)) as stratum_3, cast(null as varchar(255)) as stratum_4, cast(null as varchar(255)) as stratum_5, +COUNT_BIG(distinct person_id) as count_value +FROM @cdmDatabaseSchema.condition_occurrence co +into @scratchDatabaseSchema@schemaDelim@tempAchillesPrefix_1 +INNER JOIN ( + SELECT DISTINCT concept_id + FROM ( + SELECT c.concept_id + FROM @vocabDatabaseSchema.concept c + INNER JOIN @vocabDatabaseSchema.concept_ancestor ca + ON c.concept_id = ca.descendant_concept_id + AND ca.ancestor_concept_id = 438112 -- neoplastic disease + + UNION ALL + + SELECT concept_id + FROM @vocabDatabaseSchema.concept + WHERE concept_class_id = 'ICDO Condition' + ) +) dcc +ON co.condition_concept_id = dcc.concept_id \ No newline at end of file diff --git a/validationScripts/inst/achilles_style_dontuse/sql_server/10.sql b/validationScripts/inst/achilles_style_dontuse/sql_server/10.sql new file mode 100644 index 0000000..8c7e56e --- /dev/null +++ b/validationScripts/inst/achilles_style_dontuse/sql_server/10.sql @@ -0,0 +1,8 @@ +-- 10 Number of date of initial diagnosis modifier records + +select 10 as analysis_id, +cast(null as varchar(255)) as stratum_1, cast(null as varchar(255)) as stratum_2, cast(null as varchar(255)) as stratum_3, cast(null as varchar(255)) as stratum_4, cast(null as varchar(255)) as stratum_5, +COUNT_BIG(distinct measurement_id) as count_value +into @scratchDatabaseSchema@schemaDelim@tempAchillesPrefix_10 +FROM @cdmDatabaseSchema.measurement m +WHERE m.measurement_concept_id = 734306 -- Initial Diagnosis \ No newline at end of file diff --git a/validationScripts/inst/achilles_style_dontuse/sql_server/11.sql b/validationScripts/inst/achilles_style_dontuse/sql_server/11.sql new file mode 100644 index 0000000..4f2b071 --- /dev/null +++ b/validationScripts/inst/achilles_style_dontuse/sql_server/11.sql @@ -0,0 +1,8 @@ +-- 11 Number of persons with a date of initial diagnosis modifier record + +select 11 as analysis_id, +cast(null as varchar(255)) as stratum_1, cast(null as varchar(255)) as stratum_2, cast(null as varchar(255)) as stratum_3, cast(null as varchar(255)) as stratum_4, cast(null as varchar(255)) as stratum_5, +COUNT_BIG(distinct person_id) as count_value +into @scratchDatabaseSchema@schemaDelim@tempAchillesPrefix_11 +FROM @cdmDatabaseSchema.measurement m +WHERE m.measurement_concept_id = 734306 -- Initial Diagnosis \ No newline at end of file diff --git a/validationScripts/inst/achilles_style_dontuse/sql_server/12.sql b/validationScripts/inst/achilles_style_dontuse/sql_server/12.sql new file mode 100644 index 0000000..47e806d --- /dev/null +++ b/validationScripts/inst/achilles_style_dontuse/sql_server/12.sql @@ -0,0 +1,9 @@ +-- 12 Number of date of initial diagnosis modifier records that do not reference the condition_occurrence table + +select 12 as analysis_id, +cast(null as varchar(255)) as stratum_1, cast(null as varchar(255)) as stratum_2, cast(null as varchar(255)) as stratum_3, cast(null as varchar(255)) as stratum_4, cast(null as varchar(255)) as stratum_5, +COUNT_BIG( measurement_id) as count_value +into @scratchDatabaseSchema@schemaDelim@tempAchillesPrefix_12 +FROM measurement +WHERE measurement_concept_id = 734306 +AND meas_event_field_concept_id <> 1147127 diff --git a/validationScripts/inst/achilles_style_dontuse/sql_server/13.sql b/validationScripts/inst/achilles_style_dontuse/sql_server/13.sql new file mode 100644 index 0000000..df0b153 --- /dev/null +++ b/validationScripts/inst/achilles_style_dontuse/sql_server/13.sql @@ -0,0 +1,18 @@ +-- 13 Number of date of initial diagnosis modifier records that reference the same condition_occurrence record + +select 13 as analysis_id, +cast(null as varchar(255)) as stratum_1, cast(null as varchar(255)) as stratum_2, cast(null as varchar(255)) as stratum_3, cast(null as varchar(255)) as stratum_4, cast(null as varchar(255)) as stratum_5, +COUNT_BIG(measurement_id) as count_value +into @scratchDatabaseSchema@schemaDelim@tempAchillesPrefix_13 +FROM ( + SELECT count(condition_occurrence_id) as c, condition_occurrence_id + FROM measurement m + INNER JOIN condition_occurrence co + ON m.measurement_event_id = co.condition_occurrence_id + AND measurement_concept_id =734306 + GROUP BY co.condition_occurrence_id +) co2 +INNER JOIN measurement m2 +ON m2.measurement_event_id = co2.condition_occurrence_id +AND m2.measurement_concept_id =734306 +AND co2.c > 1 diff --git a/validationScripts/inst/achilles_style_dontuse/sql_server/14.sql b/validationScripts/inst/achilles_style_dontuse/sql_server/14.sql new file mode 100644 index 0000000..e9a39f9 --- /dev/null +++ b/validationScripts/inst/achilles_style_dontuse/sql_server/14.sql @@ -0,0 +1,19 @@ +-- 14 Number of date of initial diagnosis modifier records that have implausible dates + +select 14 as analysis_id, +cast(null as varchar(255)) as stratum_1, cast(null as varchar(255)) as stratum_2, cast(null as varchar(255)) as stratum_3, cast(null as varchar(255)) as stratum_4, cast(null as varchar(255)) as stratum_5, +COUNT_BIG(measurement_id) as count_value +into @scratchDatabaseSchema@schemaDelim@tempAchillesPrefix_14 +FROM ( + SELECT m.measurement_id + , CASE WHEN p.birth_datetime > m.measurement_datetime THEN 1 + WHEN d.death_datetime < m.measurement_datetime THEN 1 + ELSE 0 END AS invalid_measurement_datetime + FROM measurement m + INNER JOIN person p + ON m.person_id=p.person_id + AND measurement_concept_id = 734306 + LEFT JOIN death d + ON m.person_id=d.person_id +) +WHERE invalid_measurement_datetime > 0 \ No newline at end of file diff --git a/validationScripts/inst/achilles_style_dontuse/sql_server/15.sql b/validationScripts/inst/achilles_style_dontuse/sql_server/15.sql new file mode 100644 index 0000000..fc4135c --- /dev/null +++ b/validationScripts/inst/achilles_style_dontuse/sql_server/15.sql @@ -0,0 +1,10 @@ +-- 15 Number of date of initial diagnosis modifier records by measurement_type_concept_id + +select 15 as analysis_id, +cast(measurement_type_concept_id as varchar(255)) as stratum_1, +cast(null as varchar(255)) as stratum_2, cast(null as varchar(255)) as stratum_3, cast(null as varchar(255)) as stratum_4, cast(null as varchar(255)) as stratum_5, +COUNT_BIG(measurement_id) as count_value +into @scratchDatabaseSchema@schemaDelim@tempAchillesPrefix_15 +FROM measurement +WHERE measurement_concept_id = 734306 +GROUP BY measurement_type_concept_id \ No newline at end of file diff --git a/validationScripts/inst/achilles_style_dontuse/sql_server/16.sql b/validationScripts/inst/achilles_style_dontuse/sql_server/16.sql new file mode 100644 index 0000000..f2b22fc --- /dev/null +++ b/validationScripts/inst/achilles_style_dontuse/sql_server/16.sql @@ -0,0 +1,46 @@ +-- 16 Number of poorly-formed date of inital diagnosis modifier records + +select 16 as analysis_id, +cast(null as varchar(255)) as stratum_1, cast(null as varchar(255)) as stratum_2, cast(null as varchar(255)) as stratum_3, cast(null as varchar(255)) as stratum_4, cast(null as varchar(255)) as stratum_5, +COUNT_BIG(DISTINCT measurement_id) as count_value +FROM ( + + -- records not referencing condition_occurrence table + SELECT measurement_id, 'wrong table' AS reason + FROM measurement + WHERE measurement_concept_id = 734306 + AND meas_event_field_concept_id <> 1147127 + + UNION ALL + + -- condition_occurrence records with more than one "date of initial diagnosis" modifier + SELECT measurement_id, 'multiple records' AS reason + FROM ( + SELECT count(condition_occurrence_id) as c, condition_occurrence_id + FROM measurement m + INNER JOIN condition_occurrence co + ON m.measurement_event_id = co.condition_occurrence_id + AND measurement_concept_id =734306 + GROUP BY co.condition_occurrence_id + ) co2 + INNER JOIN measurement m2 + ON m2.measurement_event_id = co2.condition_occurrence_id + AND m2.measurement_concept_id =734306 + AND co2.c > 1 + + UNION ALL + + -- modified condition_concept_id is neither of class ICDO Condition nor is a descendant concept of 438112 "neoplastic disease" + SELECT measurement_id, 'wrong record type' AS reason + FROM measurement m + INNER JOIN condition_occurrence co + ON m.measurement_event_id = co.condition_occurrence_id + AND measurement_concept_id = 734306 + AND meas_event_field_concept_id <> 1147127 + LEFT JOIN concept c + ON c.concept_id = co.condition_concept_id + LEFT JOIN concept_ancestor ca + ON co.condition_concept_id = ca.descendant_concept_id + AND ca.ancestor_concept_id = 438112 -- neoplastic disease + WHERE (ca.ancestor_concept_id IS NULL AND c.concept_class_id <> 'ICDO Condition') +) \ No newline at end of file diff --git a/validationScripts/inst/achilles_style_dontuse/sql_server/2.sql b/validationScripts/inst/achilles_style_dontuse/sql_server/2.sql new file mode 100644 index 0000000..75d3a9e --- /dev/null +++ b/validationScripts/inst/achilles_style_dontuse/sql_server/2.sql @@ -0,0 +1,24 @@ +-- 2 Number of all cancer diagnoses + +select 2 as analysis_id, +cast(null as varchar(255)) as stratum_1, cast(null as varchar(255)) as stratum_2, cast(null as varchar(255)) as stratum_3, cast(null as varchar(255)) as stratum_4, cast(null as varchar(255)) as stratum_5, +COUNT_BIG(condition_occurrence_id) as count_value +into @scratchDatabaseSchema@schemaDelim@tempAchillesPrefix_2 +FROM @cdmDatabaseSchema.condition_occurrence co +INNER JOIN ( + SELECT DISTINCT concept_id + FROM ( + SELECT c.concept_id + FROM @vocabDatabaseSchema.concept c + INNER JOIN @vocabDatabaseSchema.concept_ancestor ca + ON c.concept_id = ca.descendant_concept_id + AND ca.ancestor_concept_id = 438112 -- neoplastic disease + + UNION ALL + + SELECT concept_id + FROM @vocabDatabaseSchema.concept + WHERE concept_class_id = 'ICDO Condition' + ) +) dcc +ON co.condition_concept_id = dcc.concept_id diff --git a/validationScripts/inst/achilles_style_dontuse/sql_server/3.sql b/validationScripts/inst/achilles_style_dontuse/sql_server/3.sql new file mode 100644 index 0000000..3329656 --- /dev/null +++ b/validationScripts/inst/achilles_style_dontuse/sql_server/3.sql @@ -0,0 +1,26 @@ +-- 3 Number of all cancer diagnoses by condition_concept_id + +select 3 as analysis_id, +cast(condition_concept_id as varchar(255)) as stratum_1, +cast(null as varchar(255)) as stratum_2, cast(null as varchar(255)) as stratum_3, cast(null as varchar(255)) as stratum_4, cast(null as varchar(255)) as stratum_5, +COUNT_BIG(distinct person_id) as count_value +into @scratchDatabaseSchema@schemaDelim@tempAchillesPrefix_3 +FROM @cdmDatabaseSchema.condition_occurrence co +INNER JOIN ( + SELECT DISTINCT concept_id + FROM ( + SELECT c.concept_id + FROM @vocabDatabaseSchema.concept c + INNER JOIN @vocabDatabaseSchema.concept_ancestor ca + ON c.concept_id = ca.descendant_concept_id + AND ca.ancestor_concept_id = 438112 -- neoplastic disease + + UNION ALL + + SELECT concept_id + FROM @vocabDatabaseSchema.concept + WHERE concept_class_id = 'ICDO Condition' + ) +) dcc +ON co.condition_concept_id = dcc.concept_id +GROUP BY condition_concept_id \ No newline at end of file diff --git a/validationScripts/inst/csv/onc_analysis_queries.csv b/validationScripts/inst/csv/onc_analysis_queries.csv new file mode 100644 index 0000000..415602c --- /dev/null +++ b/validationScripts/inst/csv/onc_analysis_queries.csv @@ -0,0 +1,9 @@ +"granular_analysis_id","distribution","distributed_field","analysis_name","stratum_1_name","stratum_2_name","stratum_3_name","stratum_4_name","stratum_5_name","is_default","category" +"1","0",,"Person_ids of persons with cancer diagnosis",,,,,,"1","General" +"2","0",,"Condition_occurrence_ids of cancer diagnoses",,,,,,"1","General" +"10","0",,"Measurement_ids of date of initial diagnosis modifier records",,,,,,"1","Date of Initial Diagnosis" +"11","0",,"Person_ids of persons with a date of initial diagnosis modifier record",,,,,,"1","Date of Initial Diagnosis" +"12","0",,"Measurement_ids of date of initial diagnosis modifier records that do not reference the condition_occurrence table",,,,,,"1","Date of Initial Diagnosis" +"13","0",,"Measurement_ids of date of initial diagnosis modifier records that reference the same condition_occurrence record",,,,,,"1","Date of Initial Diagnosis" +"14","0",,"Measurement_ids of date of initial diagnosis modifier records that have implausible dates",,,,,,"1","Date of Initial Diagnosis" +"15","0",,"Measurement_ids, measurement_type_concept_ids, and measurement_type_concept_names of date of initial diagnosis modifier records",,,,,,"1","Date of Initial Diagnosis" diff --git a/validationScripts/inst/description.MD b/validationScripts/inst/description.MD new file mode 100644 index 0000000..4f1855a --- /dev/null +++ b/validationScripts/inst/description.MD @@ -0,0 +1,12 @@ +Trying to figure out how to manage analyses/queries.. + +I'm toying with two styles: 1) Achilles, verbatim. 2) Based off Achilles for storage, but growing increasingly different + +Benefits of Achilles are that the analyses could all be added back into Achilles. This is a big deal as then they could integrate with Atlas or DQD and be exported to Ares where they could have distinct visualizations. + +Downside of Achilles is that it is a little restrictive and may be a bit off the mark from what I understand we are trying to do. + +Benefits of the (increasingly) ad hoc approach is that we can make the queries as granular as we would like and then combine them together to get answers to different questions. + +Also, keeping them granular makes it possible (easier) to track the records of interest (e.g. mal-formed measurement records) + diff --git a/validationScripts/inst/json/onc_analysis_composite.json b/validationScripts/inst/json/onc_analysis_composite.json new file mode 100644 index 0000000..f16a8a9 --- /dev/null +++ b/validationScripts/inst/json/onc_analysis_composite.json @@ -0,0 +1,74 @@ +{ + "composite_analyses": [ + { + "analysis_id": "1", + "analysis_name": "Number of persons with cancer diagnosis", + "analysis_type": "count", + "queries": [1], + "composite_analyses": [] + }, + { + "analysis_id": "2", + "analysis_name": "Number of cancer diagnoses", + "analysis_type": "count", + "queries": [2], + "composite_analyses": [] + }, + { + "analysis_id": "10", + "analysis_name": "Number of date of initial diagnosis modifier records", + "analysis_type": "count", + "queries": [10], + "composite_analyses": [] + }, + { + "analysis_id": "11", + "analysis_name": "Number of persons with a date of initial diagnosis modifier record", + "analysis_type": "count", + "queries": [11], + "composite_analyses": [] + }, + { + "analysis_id": "12", + "analysis_name": "Number of date of initial diagnosis modifier records that do not reference the condition_occurrence table", + "analysis_type": "count", + "queries": [12], + "composite_analyses": [] + }, + { + "analysis_id": "13", + "analysis_name": "Number of date of initial diagnosis modifier records that reference the same condition_occurrence record", + "analysis_type": "count", + "queries": [13], + "composite_analyses": [] + }, + { + "analysis_id": "14", + "analysis_name": "Number of date of initial diagnosis modifier records that have implausible dates", + "analysis_type": "count", + "queries": [14], + "composite_analyses": [] + }, + { + "analysis_id": "15", + "analysis_name": "Number of date of initial diagnosis modifier records that come from tumor registry data source", + "analysis_type": "count", + "queries": [15], + "composite_analyses": [] + }, + { + "analysis_id": "1001", + "analysis_name": "Number of poorly-formed date of initial diagnosis modifier records", + "analysis_type": "derived", + "queries": [2, 12, 13, 14], + "composite_analyses": [] + }, + { + "analysis_id": "1002", + "analysis_name": "Proportion of poorly-formed date of initial diagnosis modifier records", + "analysis_type": "proportion", + "queries": [], + "composite_analyses": [2, 1001] + } + ] +} diff --git a/validationScripts/inst/sql/composite_analyses/1.sql b/validationScripts/inst/sql/composite_analyses/1.sql new file mode 100644 index 0000000..99511f9 --- /dev/null +++ b/validationScripts/inst/sql/composite_analyses/1.sql @@ -0,0 +1,6 @@ +-- 1 Number of persons with cancer record + +select 1 as analysis_id, +cast(null as varchar(255)) as stratum_1, cast(null as varchar(255)) as stratum_2, cast(null as varchar(255)) as stratum_3, cast(null as varchar(255)) as stratum_4, cast(null as varchar(255)) as stratum_5, +COUNT_BIG(*) as count_value +FROM @scratchDatabaseSchema.onc_val_General_1 \ No newline at end of file diff --git a/validationScripts/inst/sql/composite_analyses/10.sql b/validationScripts/inst/sql/composite_analyses/10.sql new file mode 100644 index 0000000..24a7ea1 --- /dev/null +++ b/validationScripts/inst/sql/composite_analyses/10.sql @@ -0,0 +1,7 @@ +-- 10 Number of date of initial diagnosis modifier records + +select 10 as analysis_id, +cast(null as varchar(255)) as stratum_1, cast(null as varchar(255)) as stratum_2, cast(null as varchar(255)) as stratum_3, cast(null as varchar(255)) as stratum_4, cast(null as varchar(255)) as stratum_5, +COUNT_BIG(distinct measurement_id) as count_value +FROM @cdmDatabaseSchema.measurement m +WHERE m.measurement_concept_id = 734306 -- Initial Diagnosis \ No newline at end of file diff --git a/validationScripts/inst/sql/composite_analyses/1001.sql b/validationScripts/inst/sql/composite_analyses/1001.sql new file mode 100644 index 0000000..7e7df76 --- /dev/null +++ b/validationScripts/inst/sql/composite_analyses/1001.sql @@ -0,0 +1 @@ +-- 1001 Number of poorly-formed date of initial diagnosis modifier records \ No newline at end of file diff --git a/validationScripts/inst/sql/composite_analyses/1002.sql b/validationScripts/inst/sql/composite_analyses/1002.sql new file mode 100644 index 0000000..5a93213 --- /dev/null +++ b/validationScripts/inst/sql/composite_analyses/1002.sql @@ -0,0 +1 @@ +-- 1002 Proportion of poorly-formed date of initial diagnosis modifier records \ No newline at end of file diff --git a/validationScripts/inst/sql/composite_analyses/11.sql b/validationScripts/inst/sql/composite_analyses/11.sql new file mode 100644 index 0000000..dbb251d --- /dev/null +++ b/validationScripts/inst/sql/composite_analyses/11.sql @@ -0,0 +1,7 @@ +-- 11 Number of persons with a date of initial diagnosis modifier record + +select 11 as analysis_id, +cast(null as varchar(255)) as stratum_1, cast(null as varchar(255)) as stratum_2, cast(null as varchar(255)) as stratum_3, cast(null as varchar(255)) as stratum_4, cast(null as varchar(255)) as stratum_5, +COUNT_BIG(distinct person_id) as count_value +FROM @cdmDatabaseSchema.measurement m +WHERE m.measurement_concept_id = 734306 -- Initial Diagnosis \ No newline at end of file diff --git a/validationScripts/inst/sql/composite_analyses/12.sql b/validationScripts/inst/sql/composite_analyses/12.sql new file mode 100644 index 0000000..0ed272d --- /dev/null +++ b/validationScripts/inst/sql/composite_analyses/12.sql @@ -0,0 +1,46 @@ +-- 12 Number of poorly-formed date of inital diagnosis modifier records + +select 12 as analysis_id, +cast(null as varchar(255)) as stratum_1, cast(null as varchar(255)) as stratum_2, cast(null as varchar(255)) as stratum_3, cast(null as varchar(255)) as stratum_4, cast(null as varchar(255)) as stratum_5, +COUNT_BIG(DISTINCT measurement_id) as count_value +FROM ( + + -- records not referencing condition_occurrence table + SELECT measurement_id, 'wrong table' AS reason + FROM measurement + WHERE measurement_concept_id = 734306 + AND meas_event_field_concept_id <> 1147127 + + UNION ALL + + -- condition_occurrence records with more than one "date of initial diagnosis" modifier + SELECT measurement_id, 'multiple records' AS reason + FROM ( + SELECT count(condition_occurrence_id) as c, condition_occurrence_id + FROM measurement m + INNER JOIN condition_occurrence co + ON m.measurement_event_id = co.condition_occurrence_id + AND measurement_concept_id =734306 + GROUP BY co.condition_occurrence_id + ) co2 + INNER JOIN measurement m2 + ON m2.measurement_event_id = co2.condition_occurrence_id + AND m2.measurement_concept_id =734306 + AND co2.c > 1 + + UNION ALL + + -- modified condition_concept_id is neither of class ICDO Condition nor is a descendant concept of 438112 "neoplastic disease" + SELECT measurement_id, 'wrong record type' AS reason + FROM measurement m + INNER JOIN condition_occurrence co + ON m.measurement_event_id = co.condition_occurrence_id + AND measurement_concept_id = 734306 + AND meas_event_field_concept_id <> 1147127 + LEFT JOIN concept c + ON c.concept_id = co.condition_concept_id + LEFT JOIN concept_ancestor ca + ON co.condition_concept_id = ca.descendant_concept_id + AND ca.ancestor_concept_id = 438112 -- neoplastic disease + WHERE (ca.ancestor_concept_id IS NULL AND c.concept_class_id <> 'ICDO Condition') +) \ No newline at end of file diff --git a/validationScripts/inst/sql/composite_analyses/13.sql b/validationScripts/inst/sql/composite_analyses/13.sql new file mode 100644 index 0000000..fac05cb --- /dev/null +++ b/validationScripts/inst/sql/composite_analyses/13.sql @@ -0,0 +1,16 @@ +-- 13 Number of invalid dates associated with date of initial diagnosis modifier records + +SELECT count(measurement_id) +FROM ( + SELECT m.measurement_id + , CASE WHEN p.birth_datetime > m.measurement_datetime THEN 1 + WHEN d.death_datetime < m.measurement_datetime THEN 1 + ELSE 0 END AS invalid_measurement_datetime + FROM measurement m + INNER JOIN person p + ON m.person_id=p.person_id + AND measurement_concept_id = 734306 + LEFT JOIN death d + ON m.person_id=d.person_id +) +WHERE invalid_measurement_datetime > 0 \ No newline at end of file diff --git a/validationScripts/inst/sql/composite_analyses/14.sql b/validationScripts/inst/sql/composite_analyses/14.sql new file mode 100644 index 0000000..f1ae784 --- /dev/null +++ b/validationScripts/inst/sql/composite_analyses/14.sql @@ -0,0 +1 @@ +-- 14 Measurement_ids of invalid dates associated with date of initial diagnosis modifier records \ No newline at end of file diff --git a/validationScripts/inst/sql/composite_analyses/15.sql b/validationScripts/inst/sql/composite_analyses/15.sql new file mode 100644 index 0000000..897f03c --- /dev/null +++ b/validationScripts/inst/sql/composite_analyses/15.sql @@ -0,0 +1,9 @@ +-- 15 Measurement_ids, measurement_type_concept_ids, and measurement_type_concept_names of date of initial diagnosis modifier records + +SELECT measurement_id + , measurement_type_concept_id + , concept_name AS measurement_type_concept_name +FROM measurement m +INNER JOIN concept c +ON m.measurement_type_concept_id = c.concept_id +AND measurement_concept_id = 734306 \ No newline at end of file diff --git a/validationScripts/inst/sql/composite_analyses/1old.sql b/validationScripts/inst/sql/composite_analyses/1old.sql new file mode 100644 index 0000000..a7cb8a9 --- /dev/null +++ b/validationScripts/inst/sql/composite_analyses/1old.sql @@ -0,0 +1,23 @@ +-- 1 Number of persons with cancer record + +select 1 as analysis_id, +cast(null as varchar(255)) as stratum_1, cast(null as varchar(255)) as stratum_2, cast(null as varchar(255)) as stratum_3, cast(null as varchar(255)) as stratum_4, cast(null as varchar(255)) as stratum_5, +COUNT_BIG(*) as count_value +FROM @cdmDatabaseSchema.condition_occurrence co +INNER JOIN ( + SELECT DISTINCT concept_id + FROM ( + SELECT c.concept_id + FROM @vocabDatabaseSchema.concept c + INNER JOIN @vocabDatabaseSchema.concept_ancestor ca + ON c.concept_id = ca.descendant_concept_id + AND ca.ancestor_concept_id = 438112 -- neoplastic disease + + UNION ALL + + SELECT concept_id + FROM @vocabDatabaseSchema.concept + WHERE concept_class_id = 'ICDO Condition' + ) +) dcc +ON co.condition_concept_id = dcc.concept_id \ No newline at end of file diff --git a/validationScripts/inst/sql/composite_analyses/2.sql b/validationScripts/inst/sql/composite_analyses/2.sql new file mode 100644 index 0000000..f5b24b6 --- /dev/null +++ b/validationScripts/inst/sql/composite_analyses/2.sql @@ -0,0 +1,21 @@ +-- 2 Condition_occurrence_ids of cancer diagnoses + +SELECT co.condition_occurrence_id +FROM @cdmDatabaseSchema.condition_occurrence co +INNER JOIN ( + SELECT DISTINCT concept_id + FROM ( + SELECT c.concept_id + FROM @vocabDatabaseSchema.concept c + INNER JOIN @vocabDatabaseSchema.concept_ancestor ca + ON c.concept_id = ca.descendant_concept_id + AND ca.ancestor_concept_id = 438112 -- neoplastic disease + + UNION ALL + + SELECT concept_id + FROM @vocabDatabaseSchema.concept + WHERE concept_class_id = 'ICDO Condition' + ) +) dcc +ON co.condition_concept_id = dcc.concept_id \ No newline at end of file diff --git a/validationScripts/inst/sql/composite_analyses/analysisInsert.sql b/validationScripts/inst/sql/composite_analyses/analysisInsert.sql new file mode 100644 index 0000000..4efcee6 --- /dev/null +++ b/validationScripts/inst/sql/composite_analyses/analysisInsert.sql @@ -0,0 +1,4 @@ +INSERT INTO @insertSchema.onc_validation_results (analysis_id, stratum_1, stratum_2, stratum_3, stratum_4, stratum_5, count_value) +SELECT * FROM ( + @analysisText +) \ No newline at end of file diff --git a/validationScripts/inst/sql/onc_validation_analysis_ddl.sql b/validationScripts/inst/sql/onc_validation_analysis_ddl.sql new file mode 100644 index 0000000..2d1d23c --- /dev/null +++ b/validationScripts/inst/sql/onc_validation_analysis_ddl.sql @@ -0,0 +1,16 @@ +-- DDL FOR THE ONC_VALIDATION_ANALYSIS TABLE + +IF OBJECT_ID('@resultsDatabaseSchema.onc_validation_analysis', 'U') IS NOT NULL + DROP TABLE @resultsDatabaseSchema.onc_validation_analysis; + +CREATE TABLE @resultsDatabaseSchema.ONC_VALIDATION_ANALYSIS ( + analysis_id INTEGER, + analysis_name VARCHAR(255), + stratum_1_name VARCHAR(255), + stratum_2_name VARCHAR(255), + stratum_3_name VARCHAR(255), + stratum_4_name VARCHAR(255), + stratum_5_name VARCHAR(255), + is_default INTEGER, + category VARCHAR(255) +); \ No newline at end of file diff --git a/validationScripts/inst/sql/onc_validation_results_ddl.sql b/validationScripts/inst/sql/onc_validation_results_ddl.sql new file mode 100644 index 0000000..741b2d6 --- /dev/null +++ b/validationScripts/inst/sql/onc_validation_results_ddl.sql @@ -0,0 +1,14 @@ +-- DDL FOR THE ONC_VALIDATION_RESULTS TABLE + +IF OBJECT_ID('@resultsDatabaseSchema.onc_validation_results', 'U') IS NOT NULL + DROP TABLE @resultsDatabaseSchema.onc_validation_results; + +CREATE TABLE @resultsDatabaseSchema.ONC_VALIDATION_RESULTS ( + analysis_id INTEGER, + stratum_1 VARCHAR(255), + stratum_2 VARCHAR(255), + stratum_3 VARCHAR(255), + stratum_4 VARCHAR(255), + stratum_5 VARCHAR(255), + count_value BIGINT +); \ No newline at end of file diff --git a/validationScripts/inst/sql/queries/1.sql b/validationScripts/inst/sql/queries/1.sql new file mode 100644 index 0000000..9bfbda7 --- /dev/null +++ b/validationScripts/inst/sql/queries/1.sql @@ -0,0 +1,21 @@ +-- 1 Person_ids of persons with cancer diagnosis + +SELECT DISTINCT co.person_id +FROM @cdmDatabaseSchema.condition_occurrence co +INNER JOIN ( + SELECT DISTINCT concept_id + FROM ( + SELECT c.concept_id + FROM @vocabDatabaseSchema.concept c + INNER JOIN @vocabDatabaseSchema.concept_ancestor ca + ON c.concept_id = ca.descendant_concept_id + AND ca.ancestor_concept_id = 438112 -- neoplastic disease + + UNION ALL + + SELECT concept_id + FROM @vocabDatabaseSchema.concept + WHERE concept_class_id = 'ICDO Condition' + ) +) dcc +ON co.condition_concept_id = dcc.concept_id \ No newline at end of file diff --git a/validationScripts/inst/sql/queries/10.sql b/validationScripts/inst/sql/queries/10.sql new file mode 100644 index 0000000..848729e --- /dev/null +++ b/validationScripts/inst/sql/queries/10.sql @@ -0,0 +1,5 @@ +-- 10 Measurement_ids of date of initial diagnosis modifier records + +SELECT m.measurement_id +FROM @cdmDatabaseSchema.measurement m +WHERE m.measurement_concept_id = 734306 -- Initial Diagnosis \ No newline at end of file diff --git a/validationScripts/inst/sql/queries/11.sql b/validationScripts/inst/sql/queries/11.sql new file mode 100644 index 0000000..bb61d75 --- /dev/null +++ b/validationScripts/inst/sql/queries/11.sql @@ -0,0 +1,5 @@ +-- 11 Person_ids of persons with a date of initial diagnosis modifier record + +SELECT m.person_id +FROM @cdmDatabaseSchema.measurement m +WHERE m.measurement_concept_id = 734306 -- Initial Diagnosis \ No newline at end of file diff --git a/validationScripts/inst/sql/queries/12.sql b/validationScripts/inst/sql/queries/12.sql new file mode 100644 index 0000000..ef7bb3d --- /dev/null +++ b/validationScripts/inst/sql/queries/12.sql @@ -0,0 +1,6 @@ +-- 12 Measurement_ids of date of initial diagnosis modifier records that do not reference the condition_occurrence table + +SELECT m.measurement_id +FROM @cdmDatabaseSchema.measurement m +WHERE m.measurement_concept_id = 734306 +AND m.meas_event_field_concept_id <> 1147127 \ No newline at end of file diff --git a/validationScripts/inst/sql/queries/13.sql b/validationScripts/inst/sql/queries/13.sql new file mode 100644 index 0000000..f13f01f --- /dev/null +++ b/validationScripts/inst/sql/queries/13.sql @@ -0,0 +1,15 @@ +-- 13 Measurement_ids of date of initial diagnosis modifier records that reference the same condition_occurrence record + +SELECT m2.measurement_id +FROM ( + SELECT count(condition_occurrence_id) as c, condition_occurrence_id + FROM @cdmDatabaseSchema.measurement m + INNER JOIN @cdmDatabaseSchema.condition_occurrence co + ON m.measurement_event_id = co.condition_occurrence_id + AND m.measurement_concept_id =734306 + GROUP BY co.condition_occurrence_id +) co2 +INNER JOIN @cdmDatabaseSchema.measurement m2 +ON m2.measurement_event_id = co2.condition_occurrence_id +AND m2.measurement_concept_id =734306 +AND co2.c > 1 diff --git a/validationScripts/inst/sql/queries/14.sql b/validationScripts/inst/sql/queries/14.sql new file mode 100644 index 0000000..5b2cac6 --- /dev/null +++ b/validationScripts/inst/sql/queries/14.sql @@ -0,0 +1,16 @@ +-- 14 Measurement_ids of invalid dates associated with date of initial diagnosis modifier records + +SELECT m.measurement_id +FROM ( + SELECT m.measurement_id + , CASE WHEN p.birth_datetime > m.measurement_datetime THEN 1 + WHEN d.death_datetime < m.measurement_datetime THEN 1 + ELSE 0 END AS invalid_measurement_datetime + FROM @cdmDatabaseSchema.measurement m + INNER JOIN @cdmDatabaseSchema.person p + ON m.person_id=p.person_id + AND measurement_concept_id = 734306 + LEFT JOIN @cdmDatabaseSchema.death d + ON m.person_id=d.person_id +) +WHERE invalid_measurement_datetime > 0 \ No newline at end of file diff --git a/validationScripts/inst/sql/queries/15.sql b/validationScripts/inst/sql/queries/15.sql new file mode 100644 index 0000000..b44deaf --- /dev/null +++ b/validationScripts/inst/sql/queries/15.sql @@ -0,0 +1,9 @@ +-- 15 Measurement_ids, measurement_type_concept_ids, and measurement_type_concept_names of date of initial diagnosis modifier records + +SELECT m.measurement_id + , m.measurement_type_concept_id + , c.concept_name AS measurement_type_concept_name +FROM @cdmDatabaseSchema.measurement m +INNER JOIN @vocabDatabaseSchema.concept c +ON m.measurement_type_concept_id = c.concept_id +AND measurement_concept_id = 734306 \ No newline at end of file diff --git a/validationScripts/inst/sql/queries/2.sql b/validationScripts/inst/sql/queries/2.sql new file mode 100644 index 0000000..f5b24b6 --- /dev/null +++ b/validationScripts/inst/sql/queries/2.sql @@ -0,0 +1,21 @@ +-- 2 Condition_occurrence_ids of cancer diagnoses + +SELECT co.condition_occurrence_id +FROM @cdmDatabaseSchema.condition_occurrence co +INNER JOIN ( + SELECT DISTINCT concept_id + FROM ( + SELECT c.concept_id + FROM @vocabDatabaseSchema.concept c + INNER JOIN @vocabDatabaseSchema.concept_ancestor ca + ON c.concept_id = ca.descendant_concept_id + AND ca.ancestor_concept_id = 438112 -- neoplastic disease + + UNION ALL + + SELECT concept_id + FROM @vocabDatabaseSchema.concept + WHERE concept_class_id = 'ICDO Condition' + ) +) dcc +ON co.condition_concept_id = dcc.concept_id \ No newline at end of file diff --git a/validationScripts/inst/sql/queries/basicInsert.sql b/validationScripts/inst/sql/queries/basicInsert.sql new file mode 100644 index 0000000..7cd3741 --- /dev/null +++ b/validationScripts/inst/sql/queries/basicInsert.sql @@ -0,0 +1,7 @@ +-- Insert into temporary table +DROP TABLE IF EXISTS @insertSchema.@queryTableName; + +SELECT * INTO @insertSchema.@queryTableName +FROM ( + @queryText +) diff --git a/validationScripts/inst/sql/spark/readQueryTables.sql b/validationScripts/inst/sql/spark/readQueryTables.sql new file mode 100644 index 0000000..670dada --- /dev/null +++ b/validationScripts/inst/sql/spark/readQueryTables.sql @@ -0,0 +1 @@ +SHOW TABLES FROM @resultsDatabaseSchema LIKE 'onc_val_*'; \ No newline at end of file From afec565f569acaeb597214a9aa0ff43a844f3d15 Mon Sep 17 00:00:00 2001 From: Kyle Zollo-Venecek Date: Wed, 10 Jan 2024 13:55:17 -0500 Subject: [PATCH 02/10] Trim down readme --- validationScripts/README.md | 161 ------------------------------------ 1 file changed, 161 deletions(-) diff --git a/validationScripts/README.md b/validationScripts/README.md index 7cc0dad..5f5cf84 100644 --- a/validationScripts/README.md +++ b/validationScripts/README.md @@ -13,164 +13,3 @@ The star of the framework is an R Package. Along with cataloguing an extensible 3) Generating assessment results 4) Visualizing assessment results -## Analyses - -### Classes of Analyses: - -Analyses can be broken in to ____ different **classes**: - -#### Meta checks - -These analyses are very high-level and run at the very beginning of a validation test. If these tests fail, the user must correct them before the full analysis can be run. - -##### Schema correctness - - Do all relevant oncology-specific tables exist? - - Do all relevant oncology-specific tables have the required column names? - -##### Presence of development vocabulary - - Does the vocabulary table contain the "Oncology development vocabulary" record, signifying that the user has added the Oncology development vocabulary? - - If yes, is the version number the most recent Oncology development vocabulary version? - -#### Existence of Any - -These are analyses that check for the existence of anything unacceptable that must be changed - -##### Incorrect or outdated concepts being used - - Are any terms that have been destandardized by the Oncology development vocabulary being used? - -#### Proportion of cancer patients - -#### Proportion of Task group - -#### Percentage of tests passed for each Task Group - -#### Granularity of data - -## Queries - -Queries are the SQL counterpart to the analyses. Each analysis has a single query with which it is associated. The query lives in a sql file named `.sql` e.g. `1.sql`. - -### Query Classes - -The queries that are used for analyses fall into one of ___ classes: - -#### Count of records by concept sets - - Number of distinct concepts from the "correct" (standard) oncology concept set - - Total number of concepts from the "correct" (standard) oncology concept set - - Number of distinct concepts from the "incorrect" (newly destandardized/ non standard) oncology concept set - - Total number of concepts from the "incorrect" (newly destandardized/ non standard) oncology concept set - -#### Count of patients by concept sets - - How many patients have records associated with the correct/incorrect concept set? - -#### Referential integrity - - Are modifier concepts associated with the correct types of records? E.g. are stage records modifying a cancer condition concept? - - -## Quality checks - -Quality checks are captured as a series of parameterized SQL scripts that - -### Concept sets - -For each "Task Group" for which we are checking quality, we start with the set of standard concepts that may be used for mapping. - -#### Option 1 -These could be compiled in a style similar to the DQD concepts in a CSV: -`cdmTableName,cdmFieldName,conceptId,conceptName...,` - -Where every table, field, and concept group are contained in the same table and are used to parameterize multiple different generic sql scripts. - -A hard-coded list of all correct concepts and all incorrect concepts, in the same place - -#### Option 2 -Alternatively, one list of correct concepts for each "Task Group" could be maintained. To generate the list of incorrect concepts, simply query all non-standard concepts that map to the correct concepts. Along with being much easier to maintain (only need to create a list of correct/standard concepts), this would also allow us to provide suggestions on how to fix incorrect terms... - - - -## List of Analyses: - -| Task Groups | Check Set | -|-------------|-----------| -| Initial Date of Diagnosis | Simple | -| Specific Tumor Identifier | Simple | -| TNM | Full | -| Stage Group | Full | -| Grade | Full | -| Laterality | Full | -| Disease Progression | Full | -| Metastases | Full | -| Dimension | Full | -| Extension/Invasion | Full | -| Radiotherapy | Extended| -| Treatment Intent | Extended| -| Surgery | Extended| -| Drug Therapy | Extended| - - ---- - -| Type of Analysis | Check Set | -| How many records exist | Simple | -| How many records are "correct" | Simple | -| How many records are "incorrect" | Simple | -| How many patients have a record | Simple | - - - - - -TNM Analyses (and Stage Group, Grade, lateratily, disease progression, Metastases, Dimension, Exten/Invsasion) -- How many rows are correct -- How many patients have a correctly mapped TNM record (any record associated is correct) -- How many rows have a foreign key to a condition -- Proportion of rows correct -- Proportion of patients that have a correctly mapped TNM record over total number of patients with cancer diagnosis -- Proportion of rows have a foreign key to a condition -- How many rows are incorrect -- How many patients are incorrect -- How many rows have incorrectly implemented FK (NULL or not to condition) - -Initial Date of Diagnosis (specific tumor identifier) -- How many records exist (characterization) -- How many rows are correct (for in diagn, measurement_event_id points to a valid cancer diagnosis record in condition_occurrence, meas_event_field_concept_id is value 1147127 "condition_occurrence.concidtion_occurence_id") -- How many patients have a date of diagnosis record - - - -Procedures (Radiotherapy, Treatment Intent, Surgery, Drug therapy) - -Radiotherapy example: - -Radiotherapy overview -- if coming from registry: maybe say type, subtype (brachytherapy), total dosage, but not at individual fraction level. Aggregated value of everything they have -- if coming from a stanadrard EHR, will include billing code only (some coarse type-site combo, see CPT codes) -- Oncology specific EMR (Mosaiq) will have everything that registry has but also the individual fraction level (individual "round" of radiotherapy) -- If coming from NLP or flowsheet parsing: as much or similar to onc-specific EMR data - -Procedure Concept sets: -- High level radio therapy concepts -- Billing code radiotherapy concepts -- Low level radio therapy concepts - -Modifier Concept sets -- Topography (correct and incorrect sets) -- Treatment intent -- Fractions (occurrences at individual occurrence level and at aggregated level) - -Radiotherapy analyses - -## How does DQD vs Achilles handle manage analyses - ->TLDR; The checks in DQD are too general for what we are trying to do. Incorporating teh Cancer checks in their current form into DQD would require so much reworking of the DQD framework that it is unlikely the changes would ever get accepted back into a main branch. - - -### DQD - -There are 3 "levels" of checks TABLE FIELD and CONCEPT. The file inst/csv/OMOP_CDMv5.4_Check_Descriptions.csv lists all the checks, their level, name, description, Kahn class, the sqlFile that stores the check, and an "evaluationFilter"(?) - - - -SQL scripts contain the "shells" of the analyses. CSV files contain the parameters for the SQL scripts. - -Ex. concept_plausible_gender.sql takes a schema, table name, field name, concept ID, and the expected gender. The schema is passed from teh R function that executes. The rest are passed from the OMOP_CDMv5.4_Concept_Level.csv file which contains that information in a row that has a value in the column "plausibleGender". \ No newline at end of file From a128d3a81efbba394c47c86f7443cfd0ced21d9a Mon Sep 17 00:00:00 2001 From: Kyle Zollo Date: Thu, 11 Jan 2024 15:15:12 -0500 Subject: [PATCH 03/10] Create use-case.yaml --- use-case.yaml | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 use-case.yaml diff --git a/use-case.yaml b/use-case.yaml new file mode 100644 index 0000000..612d2d3 --- /dev/null +++ b/use-case.yaml @@ -0,0 +1,58 @@ +name: Use Case +description: Proposal for a new OHDSI Oncology WG Use Case. +title: "[Use Case]: " +labels: ["Use Case"] +projects: "OHDSI/13" +body: + - type: markdown + attributes: + value: | + # OHDSI Oncology WG Use Case + ## Project Deliverable + Please provide the following information about your project's deliverable: + - type: textarea + id: deliverable + attributes: + label: Description + description: "What is your deliverable?" + placeholder: "Give a description of your deliverable here" + - type: textarea + id: infrastructure + attributes: + label: Infrastructure + description: "Can this be delivered with existing Infrastructure? If no, what needs to be added?" + placeholder: "Can this be delivered with existing infrastructure?" + - type: textarea + id: timeline + attributes: + label: Timeline + description: "What is your desired timeline for completing this work?" + placeholder: "Explain your desired timeline here" + - type: textarea + id: credit + attributes: + label: Credit + description: "How would you and your team like to receive credit for this work?" + placeholder: "Can this be delivered with existing infrastructure?" + - type: textarea + id: support + attributes: + label: Support + description: "What kind of support does your team need from the workgroup?" + placeholder: "Describe the type of support you will need" + - type: textarea + id: datasets + attributes: + label: Datasets of Interest + description: "Provide a list of datasets (titles, links, descriptions, etc.) that your project is interested in using." + placeholder: "List the datasets your project is interested in using" + - type: markdown + attributes: + value: | + # Use Case Work Orders + - type: textarea + id: depends + attributes: + label: Depends On + description: "Work orders that are associated with this Use Case." + placeholder: "Issue task list goes here. This can be filled out after " From 6371e066a412bd11b8ba3bb05163fc145975f242 Mon Sep 17 00:00:00 2001 From: Robert Miller Date: Thu, 11 Jan 2024 16:53:37 -0500 Subject: [PATCH 04/10] Initial Bulk documentation edit --- docs/background.html | 217 +- docs/conventions.html | 109 +- docs/development.html | 157 +- docs/gettingInvolved.html | 89 +- docs/githubProject.html | 89 +- docs/index.html | 154 +- docs/installation.html | 676 + docs/model.html | 89 +- docs/naaccr.html | 749 + docs/progressMap.html | 89 +- docs/site_libs/font-awesome-5.1.0/css/all.css | 5 - .../webfonts/fa-brands-400.eot | Bin 115052 -> 0 bytes .../webfonts/fa-brands-400.svg | 1127 - .../webfonts/fa-brands-400.ttf | Bin 114816 -> 0 bytes .../webfonts/fa-brands-400.woff | Bin 73920 -> 0 bytes .../webfonts/fa-brands-400.woff2 | Bin 63376 -> 0 bytes .../webfonts/fa-regular-400.eot | Bin 40744 -> 0 bytes .../webfonts/fa-regular-400.svg | 467 - .../webfonts/fa-regular-400.ttf | Bin 40516 -> 0 bytes .../webfonts/fa-regular-400.woff | Bin 18212 -> 0 bytes .../webfonts/fa-regular-400.woff2 | Bin 14952 -> 0 bytes .../webfonts/fa-solid-900.eot | Bin 160768 -> 0 bytes .../webfonts/fa-solid-900.svg | 2231 -- .../webfonts/fa-solid-900.ttf | Bin 160548 -> 0 bytes .../webfonts/fa-solid-900.woff | Bin 76632 -> 0 bytes .../webfonts/fa-solid-900.woff2 | Bin 59572 -> 0 bytes docs/site_libs/font-awesome-6.4.2/css/all.css | 7968 +++++++ .../font-awesome-6.4.2/css/all.min.css | 9 + .../css/v4-shims.css | 864 +- .../font-awesome-6.4.2/css/v4-shims.min.css | 6 + .../webfonts/fa-brands-400.ttf | Bin 0 -> 189684 bytes .../webfonts/fa-brands-400.woff2 | Bin 0 -> 109808 bytes .../webfonts/fa-regular-400.ttf | Bin 0 -> 63348 bytes .../webfonts/fa-regular-400.woff2 | Bin 0 -> 24488 bytes .../webfonts/fa-solid-900.ttf | Bin 0 -> 394668 bytes .../webfonts/fa-solid-900.woff2 | Bin 0 -> 150020 bytes .../webfonts/fa-v4compatibility.ttf | Bin 0 -> 10172 bytes .../webfonts/fa-v4compatibility.woff2 | Bin 0 -> 4568 bytes .../header-attrs.js | 0 docs/site_libs/jqueryui-1.11.4/README | 8 - .../images/ui-icons_444444_256x240.png | Bin 6992 -> 0 bytes .../images/ui-icons_555555_256x240.png | Bin 6988 -> 0 bytes .../images/ui-icons_777620_256x240.png | Bin 4549 -> 0 bytes .../images/ui-icons_777777_256x240.png | Bin 6999 -> 0 bytes .../images/ui-icons_cc0000_256x240.png | Bin 4549 -> 0 bytes .../images/ui-icons_ffffff_256x240.png | Bin 6299 -> 0 bytes docs/site_libs/jqueryui-1.11.4/jquery-ui.js | 14546 ------------ .../jqueryui-1.11.4/jquery-ui.min.css | 7 - .../jqueryui-1.11.4/jquery-ui.min.js | 12 - .../jquery-ui.structure.min.css | 5 - .../jqueryui-1.11.4/jquery-ui.theme.min.css | 5 - docs/site_libs/jqueryui-1.13.2/AUTHORS.txt | 372 + docs/site_libs/jqueryui-1.13.2/LICENSE.txt | 43 + .../images/ui-icons_444444_256x240.png | Bin 0 -> 7142 bytes .../images/ui-icons_555555_256x240.png | Bin 0 -> 7126 bytes .../images/ui-icons_777620_256x240.png | Bin 0 -> 4670 bytes .../images/ui-icons_777777_256x240.png | Bin 0 -> 7163 bytes .../images/ui-icons_cc0000_256x240.png | Bin 0 -> 4670 bytes .../images/ui-icons_ffffff_256x240.png | Bin 0 -> 6539 bytes .../index.html | 144 +- .../jquery-ui.css | 847 +- docs/site_libs/jqueryui-1.13.2/jquery-ui.js | 19062 ++++++++++++++++ .../jqueryui-1.13.2/jquery-ui.min.css | 7 + .../jqueryui-1.13.2/jquery-ui.min.js | 6 + .../jquery-ui.structure.css | 736 +- .../jquery-ui.structure.min.css | 5 + .../jquery-ui.theme.css | 112 +- .../jqueryui-1.13.2/jquery-ui.theme.min.css | 5 + docs/site_libs/navigation-1.1/codefolding.js | 4 +- docs/tooling.html | 339 +- rmd/_site.yml | 31 +- rmd/background.Rmd | 107 - rmd/background.html | 541 + rmd/development.rmd | 70 + rmd/index.Rmd | 43 +- rmd/installation.Rmd | 48 + rmd/naaccr.Rmd | 108 + rmd/tooling.Rmd | 104 +- 78 files changed, 32052 insertions(+), 20360 deletions(-) create mode 100644 docs/installation.html create mode 100644 docs/naaccr.html delete mode 100644 docs/site_libs/font-awesome-5.1.0/css/all.css delete mode 100644 docs/site_libs/font-awesome-5.1.0/webfonts/fa-brands-400.eot delete mode 100644 docs/site_libs/font-awesome-5.1.0/webfonts/fa-brands-400.svg delete mode 100644 docs/site_libs/font-awesome-5.1.0/webfonts/fa-brands-400.ttf delete mode 100644 docs/site_libs/font-awesome-5.1.0/webfonts/fa-brands-400.woff delete mode 100644 docs/site_libs/font-awesome-5.1.0/webfonts/fa-brands-400.woff2 delete mode 100644 docs/site_libs/font-awesome-5.1.0/webfonts/fa-regular-400.eot delete mode 100644 docs/site_libs/font-awesome-5.1.0/webfonts/fa-regular-400.svg delete mode 100644 docs/site_libs/font-awesome-5.1.0/webfonts/fa-regular-400.ttf delete mode 100644 docs/site_libs/font-awesome-5.1.0/webfonts/fa-regular-400.woff delete mode 100644 docs/site_libs/font-awesome-5.1.0/webfonts/fa-regular-400.woff2 delete mode 100644 docs/site_libs/font-awesome-5.1.0/webfonts/fa-solid-900.eot delete mode 100644 docs/site_libs/font-awesome-5.1.0/webfonts/fa-solid-900.svg delete mode 100644 docs/site_libs/font-awesome-5.1.0/webfonts/fa-solid-900.ttf delete mode 100644 docs/site_libs/font-awesome-5.1.0/webfonts/fa-solid-900.woff delete mode 100644 docs/site_libs/font-awesome-5.1.0/webfonts/fa-solid-900.woff2 create mode 100644 docs/site_libs/font-awesome-6.4.2/css/all.css create mode 100644 docs/site_libs/font-awesome-6.4.2/css/all.min.css rename docs/site_libs/{font-awesome-5.1.0 => font-awesome-6.4.2}/css/v4-shims.css (64%) create mode 100644 docs/site_libs/font-awesome-6.4.2/css/v4-shims.min.css create mode 100644 docs/site_libs/font-awesome-6.4.2/webfonts/fa-brands-400.ttf create mode 100644 docs/site_libs/font-awesome-6.4.2/webfonts/fa-brands-400.woff2 create mode 100644 docs/site_libs/font-awesome-6.4.2/webfonts/fa-regular-400.ttf create mode 100644 docs/site_libs/font-awesome-6.4.2/webfonts/fa-regular-400.woff2 create mode 100644 docs/site_libs/font-awesome-6.4.2/webfonts/fa-solid-900.ttf create mode 100644 docs/site_libs/font-awesome-6.4.2/webfonts/fa-solid-900.woff2 create mode 100644 docs/site_libs/font-awesome-6.4.2/webfonts/fa-v4compatibility.ttf create mode 100644 docs/site_libs/font-awesome-6.4.2/webfonts/fa-v4compatibility.woff2 rename docs/site_libs/{header-attrs-2.15 => header-attrs-2.25}/header-attrs.js (100%) delete mode 100644 docs/site_libs/jqueryui-1.11.4/README delete mode 100644 docs/site_libs/jqueryui-1.11.4/images/ui-icons_444444_256x240.png delete mode 100644 docs/site_libs/jqueryui-1.11.4/images/ui-icons_555555_256x240.png delete mode 100644 docs/site_libs/jqueryui-1.11.4/images/ui-icons_777620_256x240.png delete mode 100644 docs/site_libs/jqueryui-1.11.4/images/ui-icons_777777_256x240.png delete mode 100644 docs/site_libs/jqueryui-1.11.4/images/ui-icons_cc0000_256x240.png delete mode 100644 docs/site_libs/jqueryui-1.11.4/images/ui-icons_ffffff_256x240.png delete mode 100644 docs/site_libs/jqueryui-1.11.4/jquery-ui.js delete mode 100644 docs/site_libs/jqueryui-1.11.4/jquery-ui.min.css delete mode 100644 docs/site_libs/jqueryui-1.11.4/jquery-ui.min.js delete mode 100644 docs/site_libs/jqueryui-1.11.4/jquery-ui.structure.min.css delete mode 100644 docs/site_libs/jqueryui-1.11.4/jquery-ui.theme.min.css create mode 100644 docs/site_libs/jqueryui-1.13.2/AUTHORS.txt create mode 100644 docs/site_libs/jqueryui-1.13.2/LICENSE.txt create mode 100644 docs/site_libs/jqueryui-1.13.2/images/ui-icons_444444_256x240.png create mode 100644 docs/site_libs/jqueryui-1.13.2/images/ui-icons_555555_256x240.png create mode 100644 docs/site_libs/jqueryui-1.13.2/images/ui-icons_777620_256x240.png create mode 100644 docs/site_libs/jqueryui-1.13.2/images/ui-icons_777777_256x240.png create mode 100644 docs/site_libs/jqueryui-1.13.2/images/ui-icons_cc0000_256x240.png create mode 100644 docs/site_libs/jqueryui-1.13.2/images/ui-icons_ffffff_256x240.png rename docs/site_libs/{jqueryui-1.11.4 => jqueryui-1.13.2}/index.html (89%) rename docs/site_libs/{jqueryui-1.11.4 => jqueryui-1.13.2}/jquery-ui.css (68%) create mode 100644 docs/site_libs/jqueryui-1.13.2/jquery-ui.js create mode 100644 docs/site_libs/jqueryui-1.13.2/jquery-ui.min.css create mode 100644 docs/site_libs/jqueryui-1.13.2/jquery-ui.min.js rename docs/site_libs/{jqueryui-1.11.4 => jqueryui-1.13.2}/jquery-ui.structure.css (63%) create mode 100644 docs/site_libs/jqueryui-1.13.2/jquery-ui.structure.min.css rename docs/site_libs/{jqueryui-1.11.4 => jqueryui-1.13.2}/jquery-ui.theme.css (74%) create mode 100644 docs/site_libs/jqueryui-1.13.2/jquery-ui.theme.min.css delete mode 100644 rmd/background.Rmd create mode 100644 rmd/background.html create mode 100644 rmd/installation.Rmd create mode 100644 rmd/naaccr.Rmd diff --git a/docs/background.html b/docs/background.html index aa343aa..1d48256 100644 --- a/docs/background.html +++ b/docs/background.html @@ -11,9 +11,9 @@ -background.knit + Background - + @@ -29,14 +29,14 @@ h6 {font-size: 12px;} code {color: inherit; background-color: rgba(0, 0, 0, 0.04);} pre:not([class]) { background-color: white } - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + + + +
+

Module Installation

+

This page covers how to install the Oncology CDM Extension. The +Oncology CDM Extension has not yet been incorporated +into an official OMOP CDM release. Consequently, the installation of the +Oncology CDM Extension requires additional steps beyond installing the +OMOP CDM. The Oncology CDM Extension currently supports OMOP CDM v5.31 +and the following database platforms: PostgreSQL, SQL Redshift.Server, +Oracle and

+


+
+

Step 1: Compile the OMOP CDM v5.31 Tables

+

Compile the OMOP CDM v5.31 data model tables from the OHDSI/CommonDataModel +Github repository. Find the DDL relevant to your database platform +(PostgreSQL, SQL Server, Oracle and Redshift). See +the OMOP CDM v5.31 release here. Do not yet compile indexes and +constraints. Do not yet load the vocabulary tables. We will do these in +subsequent steps.

+
+
+

Step 2: Compile the Oncology CDM Extension Tables

+
    +
  • Compile the Oncology CDM Extension data model tables from the OncologyWG Github +repository. This is a temporary additional step that will be removed +once the Oncology CDM Extension has been incorporated into an official +OMOP CDM release. Find the DDL relevant to your database platform +(PostgreSQL, Sql Server, Oracle and Redshift). See DDL +folder here.
  • +
  • For example, for the PostgreSQL database platform, execute the DDL +script. See +PostgreSQL DDL script here.
  • +
+
+
+

Step 3: Load the OMOP Standardized Vocabulary Tables.

+
    +
  • Populate the OMOP CDM vocabulary tables with, at minimum, the most +recent version of the following vocabularies: SNOMED, ICDO3, HemOnc, +RxNorm, NAACCR, and Episode Type.
  • +
  • ATHENA is the recommended approach for navigating and downloading +OHDSI vocabularies. See ATHENA +here.
  • +
  • Vocabulary ingestion scripts are organized by database platform in +the OHDSI/CommonDataModel +Github repository. Refer back to the OMOP CDM v5.31 release you +downloaded in step 1.
  • +
  • Execute the vocabulary patch file from the OncologyWG Github +repository to support metadata description/reference of the Oncology CDM +Extension’s new tables and added columns. This is a temporary additional +step that will be removed once the Oncology CDM Extension has been +incorporated into an official OMOP CDM release. The patch file currently +only supports the PostgreSQL database platform. You will need to +manually translate the vocabulary patch file to other database +platforms. See +patch file here.
  • +
  • Run the vocabulary ingestion script to populate the Oncology CDM +Extension’s newly added CONCEPT_NUMERIC vocabulary table from the OncologyWG Github +repository. This is a temporary additional step that will be removed +once the Oncology CDM Extension has been incorporated into an official +OMOP CDM release. The ingestion script depends on a CONCEPT_NUMERIC.CSV +file. Find the ingestion script (and CSV file) relevant to your database +platform (PostgreSQL, Sql Server, Oracle and Redshift). See DDL +folder here. +
  • +
+
+
+

Step 4: ETL the OMOP Standardized Clinical Data Tables

+
    +
  • This step will be institution-specific and involves transforming +your local EHR, claims database and other data sources into the common +data structures and semantics of the OMOP CDM. The OMOP CDM includes +(among others) representations for patients (PERSON), providers +(PROVIDER), healthcare facilities (CARE_SITE and LOCATION), healthcare +encounters (VISIT_OCCURRENCE), diagnoses (CONDITION_OCCURRENCE), +medication prescriptions and administrations (DRUG_EXPOSURE), labs +(MEASUREMENT), medical procedures (PROCEDURE_OCCURRENCE) and clinical +notes (NOTE). See +ETL creation best practices here.
  • +
  • Some implementations will layer the ETL of data into the Oncology +CDM Extension tables on top of an OMOP CDM instance populated from other +sources. Other implementations will ETL data into the Oncology CDM +Extension tables in a satellite OMOP CDM instance and merge the data +into a main OMOP CDM instance. This will based on local institutional +practices and strategies.
  • +
+
+
+

Step 5: Compile the indexes and constraints.

+
    +
  • Compile the OMOP CDM v5.31 indexes and constraints from the OHDSI/CommonDataModel +Github repository. Find the indexes and constraints DDL relevant to your +database platform (PostgreSQL, SQL Server, Oracle and Redshift). Refer +back to the OMOP CDM v5.31 release you downloaded in step 1.
  • +
  • Compile the Oncology CDM Extension indexes from the OncologyWG Github +repository. This is a temporary additional step that will be removed +once the Oncology CDM Extension has been incorporated into an official +OMOP CDM release. Find the indexes DDL relevant to your database +platform (PostgreSQL, Sql Server, Oracle and Redshift). See DDL +folder here. +
  • +
  • Compile the Oncology CDM Extension constraints from the OncologyWG Github +repository. This is a temporary additional step that will be removed +once the Oncology CDM Extension has been incorporated into an official +OMOP CDM release. Find the constraints DDL relevant to your database +platform (PostgreSQL, Sql Server, Oracle and Redshift). See DDL +folder here. +
  • +
+
+
+ + + +
+
+ +
+ + + + + + + + + + + + + + + + diff --git a/docs/model.html b/docs/model.html index 02aa251..169f23b 100644 --- a/docs/model.html +++ b/docs/model.html @@ -11,9 +11,9 @@ -model.knit +Oncology Model - + @@ -29,14 +29,14 @@ h6 {font-size: 12px;} code {color: inherit; background-color: rgba(0, 0, 0, 0.04);} pre:not([class]) { background-color: white } - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + + + +


+
+

ETL Overview

+

The OHDSI Oncology Development Subgroup has created a standardized +ETL to ingest NAACCR data into the Oncology CDM Extension. The ETL is a +SQL script that assumes your NAACCR data has been transformed into a +common EAV input format. The SQL script uses the common NAACCR data +dictionary input format in conjunction with the ICDO-3, NAACCR, and +Hemonc.org vocabularies within the OMOP vocabulary tables to perform the +following tasks:

+
    +
  • Mapping of ICDO-3 site and histology and diagnosis dates present in +NAACCR data to insert low-level cancer diagnoses into the +CONDITION_OCCURRENCE table and ‘Disease First Occurrence’ disease +episodes into the EPISODE table.
  • +
  • Mapping of NAACCR staging and prognostic factors present in NAACCR +data to insert low-level diagnostic modifiers into the MEASUREMENT table +pointing to the CONDITION_OCCURRENCE table and episode modifiers into +the MEASUREMENT table pointing to the ‘Disease First Occurrence’ disease +episode it inserts into the EPISODE table.
  • +
  • Mapping of NAACCR treatment variables and treatment dates present in +NAACCR data to insert low-level treatments into the PROCEDURE_OCCURRENCE +and DRUG_EXPOSURE tables and ‘Treatment Regimen’ treatment episodes into +the EPISODE table. For surgical and radiation therapy treatments, the +script places NAACCR item code values into the +EPISODE.episode_object_concept_id column. For drug treatments, the +script places into the EPISODE.episode_object_concept_id column mappings +from non-standard NAACCR items code values to Hemonc.org ‘Modality’ +concepts: +
      +
    • 35803401 Chemotherapy
    • +
    • 35803410 Immunotherapy
    • +
    • 35803407 Hormonotherapy
    • +
  • +
  • Linking child first-course ‘Treatment Regimen’ treatment episodes to +parent ‘Disease First Occurrence’ disease episode via the column +EPISODE.episode_parent_id.
  • +
  • Mapping of NAACCR treatment attribute variables present in NAACCR +data to insert low-level treatment modifiers into the MEASUREMENT table +pointing to the PROCEDURE_OCCURENECE table and episode modifiers into +the MEASUREMENT table pointing to the ‘Treatment Regimen’ treatment +episodes it inserts into the EPISODE table.
  • +
  • Inserting persons into the PERSON table if no such person_id exists +in the PERSON table. The ETL will also insert an entry in the +OBSERVATION_PERIOD table based on survival variables present in the +NAACCR data. This is to help support the strategy of ETLing data into +the Oncology CDM Extension tables in a satellite OMOP CDM instance and +merging the data into a main OMOP CDM instance.
  • +
  • Inserting a date entry in the DEATH table if the death variable +present in the NAACCR data indicates that the patient is deceased. If +prior information in the DEATH table conflicts with death data present +in the NAACCR data the ETL refrains from updating the DEATH table.
    +
  • +
  • Updates the OBSERVATION_PERIOD.observation_period_start_date and +OBSERVATION_PERIOD.observation_period_end_date for patients that have +survival variables present in the NAACCR data that indicate longer +survival.
  • +
+
+
+

NAACCR Data Dictionary

+

North American Association of Central Cancer Registries (NAACCR) is the organization that +governs the format that +is used to standardize the encoding and transmission of cancer registry +data in the United States. All healthcare facilities in the United +States that diagnose or treat cancer patients are mandated by law to +track and collect cancer data and submit it in the NAACCR data +dictionary format for all first-course diagnosed/treated primary +neoplasms.

+

The NAACCR data dictionary standard is used by multiple cancer +registry aggregators:

+ +

The NAACCR data dictionary format most importantly covers the +following areas:

+
    +
  • Demographics
  • +
  • Cancer Identification
  • +
  • Stage/Prognostic Factors
  • +
  • Treatment-1st Course
  • +
  • Follow-up/Recurrence/Death
  • +
+

NAACCR data is generally considered a gold-standard source for the +following areas:

+
    +
  • Disease First Occurrence: Fine-grained first +occurrence cancer diagnosis date and characterization via the collection +of ICDO-3 site/histology.
  • +
  • Diagnostic Modifiers: Detailed staging and +prognostic factors (clinical and pathological TNM Staging, grade, +lymphatic invasion, biomarkers, and other data points) curated from +oncology progress notes and pathology reports that are +not normally discretely captured in EHRs and or claims +databases.
  • +
  • First-course Treatment: Overall treatment modality +and high-value treatment modifiers.
  • +
  • Death and Survival.
  • +
+

NAACCR data is generally considered to be a valuable but not +gold-standard source for the following area:

+
    +
  • Disease outcomes: ‘Disease Remission’, ‘Disease +Recurrence’, ‘Disease Progression’, and ‘Disease Metastasis’.
  • +
+

The NAACCR data dictionary format is a question/answer or EAV format +that mixes: * De novo definition of data points. * Sourcing of data +points from existing standard bodies: cancer diagnosis (site/histology) +from ICDO-3 +via WHO; staging variables and values from AJCC.

+

The NAACCR data dictionary format and the source ICDO-3 vocabulary +have been ingested into the OMOP vocabulary.
+* See here +for details of how the NAACCR dictionary format has been ingested into +the OMOP vocabulary tables. * See here +for details of how the ICDO-3 vocabulary has been ingested into the OMOP +vocabulary tables. * Presently, only the source AJCC Staging Edition 7 +vocabulary has been ingested into the OMOP vocabulary tables. The OHDSI +vocabulary team is working with AJCC to cover AJCC Edition 8 and prior +editions.

+

The Hemonc.org oncology drug regimen ontology has been ingested into +the OMOP vocabulary. Some treatment NAACCR item coded values are mapped +to Hemonc.org ‘Modality’ concepts. * See here +for details of how the Hemonc.org oncology drug regimen ontology has +been ingested into the OMOP vocabulary tables.

+
+
+

Walkthrough

+


+
+

Prepare/Install

+
    +
  1. Install the Oncology CDM Extension. See here
  2. +
  3. Install the common NAACCR data dictionary input format: +NAACCR_DATA_POINTS.
    +See NAACCR_DATA_POINTS DDL here
  4. +
  5. Install an ancillary provenance table to aid data quality checks: +CDM_SOURCE_PROVENANCE. See CDM_SOURCE_PROVENANCE DDL here
  6. +
+
+
+

Populate EAV

+

The NAACCR data is natively a flat or pivoted format, typically +available to ETL developers in either the native NAACCR fixed-width file +format, XML, or in a custom relational structure determined by local +tumor registry software.

+

Currently the OHDSI Oncology Development Subgroup supports two +methods to convert and populate the NAACCR_DATA_POINTS input format from +native NAACCR data.

+
    +
  1. An R package to parse the native NAACCR flat-file format (v15-18) as +well as XML (v20-23) and ingest it into the NAACCR_DATA_POINTS input +format. The package and execution instructions can be found here.
  2. +
  3. An SQL script to transform the relational model of Elekta METRIQ +(the most popular tumor registry software). As this script references +Elkta METRIQ’s proprietary data model, it cannot be shared as open +source. For more information contact Michael Gurley, a co-lead of +the OHDSI Development Subgroup.
  4. +
+

All methods of transforming the NAACCR data to the NAACCR_DATA_POINTS +input format will need to include a method to populate the +NAACCR_DATA_POINTS.person_id column. Normally, this will be done by +mapping NAACCR item 2300 -‘Medical Record +Number’ to a medical record number in a local EHR or Enterprise +Master Patient Index (EMPI). The aforementioned R package contains a +function to populate the person identifier which assumes a database +table exists that maps MRN to person_id.

+
+
+

Execute SQL Script

+

The NAACCR ETL SQL, which translates the EAV into OMOP, has been +written in vanilla SQL to facilitate it being run on multiple different +database platforms. Currently, the OHDSI Oncology Development Subgroup +uses the SQLRender +OHDSI package to translate the NAACCR ETL to the four supported database +platforms (PostgreSQL, Sql Server, Oracle, and Redshift). The NAACCR ETL +SQL is wrapped in a database transaction to support the complete +rollback of data changes. To execute, grab the NAACCR SQL ETL from the +OncologyWG Github +repository. Find the SQL script relevant to your database platform +(PostgreSQL, Sql Server, Oracle and Redshift). See NAACCR +SQL ETL folder here.

+
+
+

Unit Testing

+

The NAACCR ETL SQL has a full-coverage unit test suite. See +here to inspect the NAACCR ETL’s unit tests.. The NAACCR ETL SQL +uses a dummy Ruby on Rails application to set up a unit testing +environment. If you would like to help develop the NAACCR SQL ETL by +making pull requests and writing unit tests to cover your changes, +please read the instructions for setting up the unit testing environment +locally. See +here instructions for setting up the NAACCR ETL unit testing +environment.

+


+


+
+
+ + + +
+
+ +
+ + + + + + + + + + + + + + + + diff --git a/docs/progressMap.html b/docs/progressMap.html index 6f90058..3bfdd22 100644 --- a/docs/progressMap.html +++ b/docs/progressMap.html @@ -11,9 +11,9 @@ -progressMap.knit + Progress Map - + @@ -29,14 +29,14 @@ h6 {font-size: 12px;} code {color: inherit; background-color: rgba(0, 0, 0, 0.04);} pre:not([class]) { background-color: white } - + - - + + " ).appendTo( body ); - } - - if(o.opacity) { // opacity option - if (this.helper.css("opacity")) { - this._storedOpacity = this.helper.css("opacity"); - } - this.helper.css("opacity", o.opacity); - } - - if(o.zIndex) { // zIndex option - if (this.helper.css("zIndex")) { - this._storedZIndex = this.helper.css("zIndex"); - } - this.helper.css("zIndex", o.zIndex); - } - - //Prepare scrolling - if(this.scrollParent[0] !== this.document[0] && this.scrollParent[0].tagName !== "HTML") { - this.overflowOffset = this.scrollParent.offset(); - } - - //Call callbacks - this._trigger("start", event, this._uiHash()); - - //Recache the helper size - if(!this._preserveHelperProportions) { - this._cacheHelperProportions(); - } - - - //Post "activate" events to possible containers - if( !noActivation ) { - for ( i = this.containers.length - 1; i >= 0; i-- ) { - this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) ); - } - } - - //Prepare possible droppables - if($.ui.ddmanager) { - $.ui.ddmanager.current = this; - } - - if ($.ui.ddmanager && !o.dropBehaviour) { - $.ui.ddmanager.prepareOffsets(this, event); - } - - this.dragging = true; - - this.helper.addClass("ui-sortable-helper"); - this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position - return true; - - }, - - _mouseDrag: function(event) { - var i, item, itemElement, intersection, - o = this.options, - scrolled = false; - - //Compute the helpers position - this.position = this._generatePosition(event); - this.positionAbs = this._convertPositionTo("absolute"); - - if (!this.lastPositionAbs) { - this.lastPositionAbs = this.positionAbs; - } - - //Do scrolling - if(this.options.scroll) { - if(this.scrollParent[0] !== this.document[0] && this.scrollParent[0].tagName !== "HTML") { - - if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) { - this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed; - } else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity) { - this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed; - } - - if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) { - this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed; - } else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity) { - this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed; - } - - } else { - - if(event.pageY - this.document.scrollTop() < o.scrollSensitivity) { - scrolled = this.document.scrollTop(this.document.scrollTop() - o.scrollSpeed); - } else if(this.window.height() - (event.pageY - this.document.scrollTop()) < o.scrollSensitivity) { - scrolled = this.document.scrollTop(this.document.scrollTop() + o.scrollSpeed); - } - - if(event.pageX - this.document.scrollLeft() < o.scrollSensitivity) { - scrolled = this.document.scrollLeft(this.document.scrollLeft() - o.scrollSpeed); - } else if(this.window.width() - (event.pageX - this.document.scrollLeft()) < o.scrollSensitivity) { - scrolled = this.document.scrollLeft(this.document.scrollLeft() + o.scrollSpeed); - } - - } - - if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) { - $.ui.ddmanager.prepareOffsets(this, event); - } - } - - //Regenerate the absolute position used for position checks - this.positionAbs = this._convertPositionTo("absolute"); - - //Set the helper position - if(!this.options.axis || this.options.axis !== "y") { - this.helper[0].style.left = this.position.left+"px"; - } - if(!this.options.axis || this.options.axis !== "x") { - this.helper[0].style.top = this.position.top+"px"; - } - - //Rearrange - for (i = this.items.length - 1; i >= 0; i--) { - - //Cache variables and intersection, continue if no intersection - item = this.items[i]; - itemElement = item.item[0]; - intersection = this._intersectsWithPointer(item); - if (!intersection) { - continue; - } - - // Only put the placeholder inside the current Container, skip all - // items from other containers. This works because when moving - // an item from one container to another the - // currentContainer is switched before the placeholder is moved. - // - // Without this, moving items in "sub-sortables" can cause - // the placeholder to jitter between the outer and inner container. - if (item.instance !== this.currentContainer) { - continue; - } - - // cannot intersect with itself - // no useless actions that have been done before - // no action if the item moved is the parent of the item checked - if (itemElement !== this.currentItem[0] && - this.placeholder[intersection === 1 ? "next" : "prev"]()[0] !== itemElement && - !$.contains(this.placeholder[0], itemElement) && - (this.options.type === "semi-dynamic" ? !$.contains(this.element[0], itemElement) : true) - ) { - - this.direction = intersection === 1 ? "down" : "up"; - - if (this.options.tolerance === "pointer" || this._intersectsWithSides(item)) { - this._rearrange(event, item); - } else { - break; - } - - this._trigger("change", event, this._uiHash()); - break; - } - } - - //Post events to containers - this._contactContainers(event); - - //Interconnect with droppables - if($.ui.ddmanager) { - $.ui.ddmanager.drag(this, event); - } - - //Call callbacks - this._trigger("sort", event, this._uiHash()); - - this.lastPositionAbs = this.positionAbs; - return false; - - }, - - _mouseStop: function(event, noPropagation) { - - if(!event) { - return; - } - - //If we are using droppables, inform the manager about the drop - if ($.ui.ddmanager && !this.options.dropBehaviour) { - $.ui.ddmanager.drop(this, event); - } - - if(this.options.revert) { - var that = this, - cur = this.placeholder.offset(), - axis = this.options.axis, - animation = {}; - - if ( !axis || axis === "x" ) { - animation.left = cur.left - this.offset.parent.left - this.margins.left + (this.offsetParent[0] === this.document[0].body ? 0 : this.offsetParent[0].scrollLeft); - } - if ( !axis || axis === "y" ) { - animation.top = cur.top - this.offset.parent.top - this.margins.top + (this.offsetParent[0] === this.document[0].body ? 0 : this.offsetParent[0].scrollTop); - } - this.reverting = true; - $(this.helper).animate( animation, parseInt(this.options.revert, 10) || 500, function() { - that._clear(event); - }); - } else { - this._clear(event, noPropagation); - } - - return false; - - }, - - cancel: function() { - - if(this.dragging) { - - this._mouseUp({ target: null }); - - if(this.options.helper === "original") { - this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"); - } else { - this.currentItem.show(); - } - - //Post deactivating events to containers - for (var i = this.containers.length - 1; i >= 0; i--){ - this.containers[i]._trigger("deactivate", null, this._uiHash(this)); - if(this.containers[i].containerCache.over) { - this.containers[i]._trigger("out", null, this._uiHash(this)); - this.containers[i].containerCache.over = 0; - } - } - - } - - if (this.placeholder) { - //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node! - if(this.placeholder[0].parentNode) { - this.placeholder[0].parentNode.removeChild(this.placeholder[0]); - } - if(this.options.helper !== "original" && this.helper && this.helper[0].parentNode) { - this.helper.remove(); - } - - $.extend(this, { - helper: null, - dragging: false, - reverting: false, - _noFinalSort: null - }); - - if(this.domPosition.prev) { - $(this.domPosition.prev).after(this.currentItem); - } else { - $(this.domPosition.parent).prepend(this.currentItem); - } - } - - return this; - - }, - - serialize: function(o) { - - var items = this._getItemsAsjQuery(o && o.connected), - str = []; - o = o || {}; - - $(items).each(function() { - var res = ($(o.item || this).attr(o.attribute || "id") || "").match(o.expression || (/(.+)[\-=_](.+)/)); - if (res) { - str.push((o.key || res[1]+"[]")+"="+(o.key && o.expression ? res[1] : res[2])); - } - }); - - if(!str.length && o.key) { - str.push(o.key + "="); - } - - return str.join("&"); - - }, - - toArray: function(o) { - - var items = this._getItemsAsjQuery(o && o.connected), - ret = []; - - o = o || {}; - - items.each(function() { ret.push($(o.item || this).attr(o.attribute || "id") || ""); }); - return ret; - - }, - - /* Be careful with the following core functions */ - _intersectsWith: function(item) { - - var x1 = this.positionAbs.left, - x2 = x1 + this.helperProportions.width, - y1 = this.positionAbs.top, - y2 = y1 + this.helperProportions.height, - l = item.left, - r = l + item.width, - t = item.top, - b = t + item.height, - dyClick = this.offset.click.top, - dxClick = this.offset.click.left, - isOverElementHeight = ( this.options.axis === "x" ) || ( ( y1 + dyClick ) > t && ( y1 + dyClick ) < b ), - isOverElementWidth = ( this.options.axis === "y" ) || ( ( x1 + dxClick ) > l && ( x1 + dxClick ) < r ), - isOverElement = isOverElementHeight && isOverElementWidth; - - if ( this.options.tolerance === "pointer" || - this.options.forcePointerForContainers || - (this.options.tolerance !== "pointer" && this.helperProportions[this.floating ? "width" : "height"] > item[this.floating ? "width" : "height"]) - ) { - return isOverElement; - } else { - - return (l < x1 + (this.helperProportions.width / 2) && // Right Half - x2 - (this.helperProportions.width / 2) < r && // Left Half - t < y1 + (this.helperProportions.height / 2) && // Bottom Half - y2 - (this.helperProportions.height / 2) < b ); // Top Half - - } - }, - - _intersectsWithPointer: function(item) { - - var isOverElementHeight = (this.options.axis === "x") || this._isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height), - isOverElementWidth = (this.options.axis === "y") || this._isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width), - isOverElement = isOverElementHeight && isOverElementWidth, - verticalDirection = this._getDragVerticalDirection(), - horizontalDirection = this._getDragHorizontalDirection(); - - if (!isOverElement) { - return false; - } - - return this.floating ? - ( ((horizontalDirection && horizontalDirection === "right") || verticalDirection === "down") ? 2 : 1 ) - : ( verticalDirection && (verticalDirection === "down" ? 2 : 1) ); - - }, - - _intersectsWithSides: function(item) { - - var isOverBottomHalf = this._isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height), - isOverRightHalf = this._isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width), - verticalDirection = this._getDragVerticalDirection(), - horizontalDirection = this._getDragHorizontalDirection(); - - if (this.floating && horizontalDirection) { - return ((horizontalDirection === "right" && isOverRightHalf) || (horizontalDirection === "left" && !isOverRightHalf)); - } else { - return verticalDirection && ((verticalDirection === "down" && isOverBottomHalf) || (verticalDirection === "up" && !isOverBottomHalf)); - } - - }, - - _getDragVerticalDirection: function() { - var delta = this.positionAbs.top - this.lastPositionAbs.top; - return delta !== 0 && (delta > 0 ? "down" : "up"); - }, - - _getDragHorizontalDirection: function() { - var delta = this.positionAbs.left - this.lastPositionAbs.left; - return delta !== 0 && (delta > 0 ? "right" : "left"); - }, - - refresh: function(event) { - this._refreshItems(event); - this._setHandleClassName(); - this.refreshPositions(); - return this; - }, - - _connectWith: function() { - var options = this.options; - return options.connectWith.constructor === String ? [options.connectWith] : options.connectWith; - }, - - _getItemsAsjQuery: function(connected) { - - var i, j, cur, inst, - items = [], - queries = [], - connectWith = this._connectWith(); - - if(connectWith && connected) { - for (i = connectWith.length - 1; i >= 0; i--){ - cur = $(connectWith[i], this.document[0]); - for ( j = cur.length - 1; j >= 0; j--){ - inst = $.data(cur[j], this.widgetFullName); - if(inst && inst !== this && !inst.options.disabled) { - queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), inst]); - } - } - } - } - - queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), this]); - - function addItems() { - items.push( this ); - } - for (i = queries.length - 1; i >= 0; i--){ - queries[i][0].each( addItems ); - } - - return $(items); - - }, - - _removeCurrentsFromItems: function() { - - var list = this.currentItem.find(":data(" + this.widgetName + "-item)"); - - this.items = $.grep(this.items, function (item) { - for (var j=0; j < list.length; j++) { - if(list[j] === item.item[0]) { - return false; - } - } - return true; - }); - - }, - - _refreshItems: function(event) { - - this.items = []; - this.containers = [this]; - - var i, j, cur, inst, targetData, _queries, item, queriesLength, - items = this.items, - queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]], - connectWith = this._connectWith(); - - if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down - for (i = connectWith.length - 1; i >= 0; i--){ - cur = $(connectWith[i], this.document[0]); - for (j = cur.length - 1; j >= 0; j--){ - inst = $.data(cur[j], this.widgetFullName); - if(inst && inst !== this && !inst.options.disabled) { - queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]); - this.containers.push(inst); - } - } - } - } - - for (i = queries.length - 1; i >= 0; i--) { - targetData = queries[i][1]; - _queries = queries[i][0]; - - for (j=0, queriesLength = _queries.length; j < queriesLength; j++) { - item = $(_queries[j]); - - item.data(this.widgetName + "-item", targetData); // Data for target checking (mouse manager) - - items.push({ - item: item, - instance: targetData, - width: 0, height: 0, - left: 0, top: 0 - }); - } - } - - }, - - refreshPositions: function(fast) { - - // Determine whether items are being displayed horizontally - this.floating = this.items.length ? - this.options.axis === "x" || this._isFloating( this.items[ 0 ].item ) : - false; - - //This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change - if(this.offsetParent && this.helper) { - this.offset.parent = this._getParentOffset(); - } - - var i, item, t, p; - - for (i = this.items.length - 1; i >= 0; i--){ - item = this.items[i]; - - //We ignore calculating positions of all connected containers when we're not over them - if(item.instance !== this.currentContainer && this.currentContainer && item.item[0] !== this.currentItem[0]) { - continue; - } - - t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item; - - if (!fast) { - item.width = t.outerWidth(); - item.height = t.outerHeight(); - } - - p = t.offset(); - item.left = p.left; - item.top = p.top; - } - - if(this.options.custom && this.options.custom.refreshContainers) { - this.options.custom.refreshContainers.call(this); - } else { - for (i = this.containers.length - 1; i >= 0; i--){ - p = this.containers[i].element.offset(); - this.containers[i].containerCache.left = p.left; - this.containers[i].containerCache.top = p.top; - this.containers[i].containerCache.width = this.containers[i].element.outerWidth(); - this.containers[i].containerCache.height = this.containers[i].element.outerHeight(); - } - } - - return this; - }, - - _createPlaceholder: function(that) { - that = that || this; - var className, - o = that.options; - - if(!o.placeholder || o.placeholder.constructor === String) { - className = o.placeholder; - o.placeholder = { - element: function() { - - var nodeName = that.currentItem[0].nodeName.toLowerCase(), - element = $( "<" + nodeName + ">", that.document[0] ) - .addClass(className || that.currentItem[0].className+" ui-sortable-placeholder") - .removeClass("ui-sortable-helper"); - - if ( nodeName === "tbody" ) { - that._createTrPlaceholder( - that.currentItem.find( "tr" ).eq( 0 ), - $( "", that.document[ 0 ] ).appendTo( element ) - ); - } else if ( nodeName === "tr" ) { - that._createTrPlaceholder( that.currentItem, element ); - } else if ( nodeName === "img" ) { - element.attr( "src", that.currentItem.attr( "src" ) ); - } - - if ( !className ) { - element.css( "visibility", "hidden" ); - } - - return element; - }, - update: function(container, p) { - - // 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that - // 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified - if(className && !o.forcePlaceholderSize) { - return; - } - - //If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item - if(!p.height()) { p.height(that.currentItem.innerHeight() - parseInt(that.currentItem.css("paddingTop")||0, 10) - parseInt(that.currentItem.css("paddingBottom")||0, 10)); } - if(!p.width()) { p.width(that.currentItem.innerWidth() - parseInt(that.currentItem.css("paddingLeft")||0, 10) - parseInt(that.currentItem.css("paddingRight")||0, 10)); } - } - }; - } - - //Create the placeholder - that.placeholder = $(o.placeholder.element.call(that.element, that.currentItem)); - - //Append it after the actual current item - that.currentItem.after(that.placeholder); - - //Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317) - o.placeholder.update(that, that.placeholder); - - }, - - _createTrPlaceholder: function( sourceTr, targetTr ) { - var that = this; - - sourceTr.children().each(function() { - $( " ", that.document[ 0 ] ) - .attr( "colspan", $( this ).attr( "colspan" ) || 1 ) - .appendTo( targetTr ); - }); - }, - - _contactContainers: function(event) { - var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, cur, nearBottom, floating, axis, - innermostContainer = null, - innermostIndex = null; - - // get innermost container that intersects with item - for (i = this.containers.length - 1; i >= 0; i--) { - - // never consider a container that's located within the item itself - if($.contains(this.currentItem[0], this.containers[i].element[0])) { - continue; - } - - if(this._intersectsWith(this.containers[i].containerCache)) { - - // if we've already found a container and it's more "inner" than this, then continue - if(innermostContainer && $.contains(this.containers[i].element[0], innermostContainer.element[0])) { - continue; - } - - innermostContainer = this.containers[i]; - innermostIndex = i; - - } else { - // container doesn't intersect. trigger "out" event if necessary - if(this.containers[i].containerCache.over) { - this.containers[i]._trigger("out", event, this._uiHash(this)); - this.containers[i].containerCache.over = 0; - } - } - - } - - // if no intersecting containers found, return - if(!innermostContainer) { - return; - } - - // move the item into the container if it's not there already - if(this.containers.length === 1) { - if (!this.containers[innermostIndex].containerCache.over) { - this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); - this.containers[innermostIndex].containerCache.over = 1; - } - } else { - - //When entering a new container, we will find the item with the least distance and append our item near it - dist = 10000; - itemWithLeastDistance = null; - floating = innermostContainer.floating || this._isFloating(this.currentItem); - posProperty = floating ? "left" : "top"; - sizeProperty = floating ? "width" : "height"; - axis = floating ? "clientX" : "clientY"; - - for (j = this.items.length - 1; j >= 0; j--) { - if(!$.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) { - continue; - } - if(this.items[j].item[0] === this.currentItem[0]) { - continue; - } - - cur = this.items[j].item.offset()[posProperty]; - nearBottom = false; - if ( event[ axis ] - cur > this.items[ j ][ sizeProperty ] / 2 ) { - nearBottom = true; - } - - if ( Math.abs( event[ axis ] - cur ) < dist ) { - dist = Math.abs( event[ axis ] - cur ); - itemWithLeastDistance = this.items[ j ]; - this.direction = nearBottom ? "up": "down"; - } - } - - //Check if dropOnEmpty is enabled - if(!itemWithLeastDistance && !this.options.dropOnEmpty) { - return; - } - - if(this.currentContainer === this.containers[innermostIndex]) { - if ( !this.currentContainer.containerCache.over ) { - this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash() ); - this.currentContainer.containerCache.over = 1; - } - return; - } - - itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true); - this._trigger("change", event, this._uiHash()); - this.containers[innermostIndex]._trigger("change", event, this._uiHash(this)); - this.currentContainer = this.containers[innermostIndex]; - - //Update the placeholder - this.options.placeholder.update(this.currentContainer, this.placeholder); - - this.containers[innermostIndex]._trigger("over", event, this._uiHash(this)); - this.containers[innermostIndex].containerCache.over = 1; - } - - - }, - - _createHelper: function(event) { - - var o = this.options, - helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper === "clone" ? this.currentItem.clone() : this.currentItem); - - //Add the helper to the DOM if that didn't happen already - if(!helper.parents("body").length) { - $(o.appendTo !== "parent" ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]); - } - - if(helper[0] === this.currentItem[0]) { - this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") }; - } - - if(!helper[0].style.width || o.forceHelperSize) { - helper.width(this.currentItem.width()); - } - if(!helper[0].style.height || o.forceHelperSize) { - helper.height(this.currentItem.height()); - } - - return helper; - - }, - - _adjustOffsetFromHelper: function(obj) { - if (typeof obj === "string") { - obj = obj.split(" "); - } - if ($.isArray(obj)) { - obj = {left: +obj[0], top: +obj[1] || 0}; - } - if ("left" in obj) { - this.offset.click.left = obj.left + this.margins.left; - } - if ("right" in obj) { - this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left; - } - if ("top" in obj) { - this.offset.click.top = obj.top + this.margins.top; - } - if ("bottom" in obj) { - this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top; - } - }, - - _getParentOffset: function() { - - - //Get the offsetParent and cache its position - this.offsetParent = this.helper.offsetParent(); - var po = this.offsetParent.offset(); - - // This is a special case where we need to modify a offset calculated on start, since the following happened: - // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent - // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that - // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag - if(this.cssPosition === "absolute" && this.scrollParent[0] !== this.document[0] && $.contains(this.scrollParent[0], this.offsetParent[0])) { - po.left += this.scrollParent.scrollLeft(); - po.top += this.scrollParent.scrollTop(); - } - - // This needs to be actually done for all browsers, since pageX/pageY includes this information - // with an ugly IE fix - if( this.offsetParent[0] === this.document[0].body || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) { - po = { top: 0, left: 0 }; - } - - return { - top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0), - left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0) - }; - - }, - - _getRelativeOffset: function() { - - if(this.cssPosition === "relative") { - var p = this.currentItem.position(); - return { - top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(), - left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft() - }; - } else { - return { top: 0, left: 0 }; - } - - }, - - _cacheMargins: function() { - this.margins = { - left: (parseInt(this.currentItem.css("marginLeft"),10) || 0), - top: (parseInt(this.currentItem.css("marginTop"),10) || 0) - }; - }, - - _cacheHelperProportions: function() { - this.helperProportions = { - width: this.helper.outerWidth(), - height: this.helper.outerHeight() - }; - }, - - _setContainment: function() { - - var ce, co, over, - o = this.options; - if(o.containment === "parent") { - o.containment = this.helper[0].parentNode; - } - if(o.containment === "document" || o.containment === "window") { - this.containment = [ - 0 - this.offset.relative.left - this.offset.parent.left, - 0 - this.offset.relative.top - this.offset.parent.top, - o.containment === "document" ? this.document.width() : this.window.width() - this.helperProportions.width - this.margins.left, - (o.containment === "document" ? this.document.width() : this.window.height() || this.document[0].body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top - ]; - } - - if(!(/^(document|window|parent)$/).test(o.containment)) { - ce = $(o.containment)[0]; - co = $(o.containment).offset(); - over = ($(ce).css("overflow") !== "hidden"); - - this.containment = [ - co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left, - co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top, - co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left, - co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top - ]; - } - - }, - - _convertPositionTo: function(d, pos) { - - if(!pos) { - pos = this.position; - } - var mod = d === "absolute" ? 1 : -1, - scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== this.document[0] && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, - scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName); - - return { - top: ( - pos.top + // The absolute mouse position - this.offset.relative.top * mod + // Only for relative positioned nodes: Relative offset from element to offset parent - this.offset.parent.top * mod - // The offsetParent's offset without borders (offset + border) - ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod) - ), - left: ( - pos.left + // The absolute mouse position - this.offset.relative.left * mod + // Only for relative positioned nodes: Relative offset from element to offset parent - this.offset.parent.left * mod - // The offsetParent's offset without borders (offset + border) - ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod) - ) - }; - - }, - - _generatePosition: function(event) { - - var top, left, - o = this.options, - pageX = event.pageX, - pageY = event.pageY, - scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== this.document[0] && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName); - - // This is another very weird special case that only happens for relative elements: - // 1. If the css position is relative - // 2. and the scroll parent is the document or similar to the offset parent - // we have to refresh the relative offset during the scroll so there are no jumps - if(this.cssPosition === "relative" && !(this.scrollParent[0] !== this.document[0] && this.scrollParent[0] !== this.offsetParent[0])) { - this.offset.relative = this._getRelativeOffset(); - } - - /* - * - Position constraining - - * Constrain the position to a mix of grid, containment. - */ - - if(this.originalPosition) { //If we are not dragging yet, we won't check for options - - if(this.containment) { - if(event.pageX - this.offset.click.left < this.containment[0]) { - pageX = this.containment[0] + this.offset.click.left; - } - if(event.pageY - this.offset.click.top < this.containment[1]) { - pageY = this.containment[1] + this.offset.click.top; - } - if(event.pageX - this.offset.click.left > this.containment[2]) { - pageX = this.containment[2] + this.offset.click.left; - } - if(event.pageY - this.offset.click.top > this.containment[3]) { - pageY = this.containment[3] + this.offset.click.top; - } - } - - if(o.grid) { - top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1]; - pageY = this.containment ? ( (top - this.offset.click.top >= this.containment[1] && top - this.offset.click.top <= this.containment[3]) ? top : ((top - this.offset.click.top >= this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top; - - left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0]; - pageX = this.containment ? ( (left - this.offset.click.left >= this.containment[0] && left - this.offset.click.left <= this.containment[2]) ? left : ((left - this.offset.click.left >= this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left; - } - - } - - return { - top: ( - pageY - // The absolute mouse position - this.offset.click.top - // Click offset (relative to the element) - this.offset.relative.top - // Only for relative positioned nodes: Relative offset from element to offset parent - this.offset.parent.top + // The offsetParent's offset without borders (offset + border) - ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) )) - ), - left: ( - pageX - // The absolute mouse position - this.offset.click.left - // Click offset (relative to the element) - this.offset.relative.left - // Only for relative positioned nodes: Relative offset from element to offset parent - this.offset.parent.left + // The offsetParent's offset without borders (offset + border) - ( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() )) - ) - }; - - }, - - _rearrange: function(event, i, a, hardRefresh) { - - a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction === "down" ? i.item[0] : i.item[0].nextSibling)); - - //Various things done here to improve the performance: - // 1. we create a setTimeout, that calls refreshPositions - // 2. on the instance, we have a counter variable, that get's higher after every append - // 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same - // 4. this lets only the last addition to the timeout stack through - this.counter = this.counter ? ++this.counter : 1; - var counter = this.counter; - - this._delay(function() { - if(counter === this.counter) { - this.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove - } - }); - - }, - - _clear: function(event, noPropagation) { - - this.reverting = false; - // We delay all events that have to be triggered to after the point where the placeholder has been removed and - // everything else normalized again - var i, - delayedTriggers = []; - - // We first have to update the dom position of the actual currentItem - // Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088) - if(!this._noFinalSort && this.currentItem.parent().length) { - this.placeholder.before(this.currentItem); - } - this._noFinalSort = null; - - if(this.helper[0] === this.currentItem[0]) { - for(i in this._storedCSS) { - if(this._storedCSS[i] === "auto" || this._storedCSS[i] === "static") { - this._storedCSS[i] = ""; - } - } - this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper"); - } else { - this.currentItem.show(); - } - - if(this.fromOutside && !noPropagation) { - delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); }); - } - if((this.fromOutside || this.domPosition.prev !== this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent !== this.currentItem.parent()[0]) && !noPropagation) { - delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed - } - - // Check if the items Container has Changed and trigger appropriate - // events. - if (this !== this.currentContainer) { - if(!noPropagation) { - delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); }); - delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.currentContainer)); - delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.currentContainer)); - } - } - - - //Post events to containers - function delayEvent( type, instance, container ) { - return function( event ) { - container._trigger( type, event, instance._uiHash( instance ) ); - }; - } - for (i = this.containers.length - 1; i >= 0; i--){ - if (!noPropagation) { - delayedTriggers.push( delayEvent( "deactivate", this, this.containers[ i ] ) ); - } - if(this.containers[i].containerCache.over) { - delayedTriggers.push( delayEvent( "out", this, this.containers[ i ] ) ); - this.containers[i].containerCache.over = 0; - } - } - - //Do what was originally in plugins - if ( this.storedCursor ) { - this.document.find( "body" ).css( "cursor", this.storedCursor ); - this.storedStylesheet.remove(); - } - if(this._storedOpacity) { - this.helper.css("opacity", this._storedOpacity); - } - if(this._storedZIndex) { - this.helper.css("zIndex", this._storedZIndex === "auto" ? "" : this._storedZIndex); - } - - this.dragging = false; - - if(!noPropagation) { - this._trigger("beforeStop", event, this._uiHash()); - } - - //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node! - this.placeholder[0].parentNode.removeChild(this.placeholder[0]); - - if ( !this.cancelHelperRemoval ) { - if ( this.helper[ 0 ] !== this.currentItem[ 0 ] ) { - this.helper.remove(); - } - this.helper = null; - } - - if(!noPropagation) { - for (i=0; i < delayedTriggers.length; i++) { - delayedTriggers[i].call(this, event); - } //Trigger all delayed events - this._trigger("stop", event, this._uiHash()); - } - - this.fromOutside = false; - return !this.cancelHelperRemoval; - - }, - - _trigger: function() { - if ($.Widget.prototype._trigger.apply(this, arguments) === false) { - this.cancel(); - } - }, - - _uiHash: function(_inst) { - var inst = _inst || this; - return { - helper: inst.helper, - placeholder: inst.placeholder || $([]), - position: inst.position, - originalPosition: inst.originalPosition, - offset: inst.positionAbs, - item: inst.currentItem, - sender: _inst ? _inst.element : null - }; - } - -}); - - -/*! - * jQuery UI Accordion 1.11.4 - * http://jqueryui.com - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - * - * http://api.jqueryui.com/accordion/ - */ - - -var accordion = $.widget( "ui.accordion", { - version: "1.11.4", - options: { - active: 0, - animate: {}, - collapsible: false, - event: "click", - header: "> li > :first-child,> :not(li):even", - heightStyle: "auto", - icons: { - activeHeader: "ui-icon-triangle-1-s", - header: "ui-icon-triangle-1-e" - }, - - // callbacks - activate: null, - beforeActivate: null - }, - - hideProps: { - borderTopWidth: "hide", - borderBottomWidth: "hide", - paddingTop: "hide", - paddingBottom: "hide", - height: "hide" - }, - - showProps: { - borderTopWidth: "show", - borderBottomWidth: "show", - paddingTop: "show", - paddingBottom: "show", - height: "show" - }, - - _create: function() { - var options = this.options; - this.prevShow = this.prevHide = $(); - this.element.addClass( "ui-accordion ui-widget ui-helper-reset" ) - // ARIA - .attr( "role", "tablist" ); - - // don't allow collapsible: false and active: false / null - if ( !options.collapsible && (options.active === false || options.active == null) ) { - options.active = 0; - } - - this._processPanels(); - // handle negative values - if ( options.active < 0 ) { - options.active += this.headers.length; - } - this._refresh(); - }, - - _getCreateEventData: function() { - return { - header: this.active, - panel: !this.active.length ? $() : this.active.next() - }; - }, - - _createIcons: function() { - var icons = this.options.icons; - if ( icons ) { - $( "" ) - .addClass( "ui-accordion-header-icon ui-icon " + icons.header ) - .prependTo( this.headers ); - this.active.children( ".ui-accordion-header-icon" ) - .removeClass( icons.header ) - .addClass( icons.activeHeader ); - this.headers.addClass( "ui-accordion-icons" ); - } - }, - - _destroyIcons: function() { - this.headers - .removeClass( "ui-accordion-icons" ) - .children( ".ui-accordion-header-icon" ) - .remove(); - }, - - _destroy: function() { - var contents; - - // clean up main element - this.element - .removeClass( "ui-accordion ui-widget ui-helper-reset" ) - .removeAttr( "role" ); - - // clean up headers - this.headers - .removeClass( "ui-accordion-header ui-accordion-header-active ui-state-default " + - "ui-corner-all ui-state-active ui-state-disabled ui-corner-top" ) - .removeAttr( "role" ) - .removeAttr( "aria-expanded" ) - .removeAttr( "aria-selected" ) - .removeAttr( "aria-controls" ) - .removeAttr( "tabIndex" ) - .removeUniqueId(); - - this._destroyIcons(); - - // clean up content panels - contents = this.headers.next() - .removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom " + - "ui-accordion-content ui-accordion-content-active ui-state-disabled" ) - .css( "display", "" ) - .removeAttr( "role" ) - .removeAttr( "aria-hidden" ) - .removeAttr( "aria-labelledby" ) - .removeUniqueId(); - - if ( this.options.heightStyle !== "content" ) { - contents.css( "height", "" ); - } - }, - - _setOption: function( key, value ) { - if ( key === "active" ) { - // _activate() will handle invalid values and update this.options - this._activate( value ); - return; - } - - if ( key === "event" ) { - if ( this.options.event ) { - this._off( this.headers, this.options.event ); - } - this._setupEvents( value ); - } - - this._super( key, value ); - - // setting collapsible: false while collapsed; open first panel - if ( key === "collapsible" && !value && this.options.active === false ) { - this._activate( 0 ); - } - - if ( key === "icons" ) { - this._destroyIcons(); - if ( value ) { - this._createIcons(); - } - } - - // #5332 - opacity doesn't cascade to positioned elements in IE - // so we need to add the disabled class to the headers and panels - if ( key === "disabled" ) { - this.element - .toggleClass( "ui-state-disabled", !!value ) - .attr( "aria-disabled", value ); - this.headers.add( this.headers.next() ) - .toggleClass( "ui-state-disabled", !!value ); - } - }, - - _keydown: function( event ) { - if ( event.altKey || event.ctrlKey ) { - return; - } - - var keyCode = $.ui.keyCode, - length = this.headers.length, - currentIndex = this.headers.index( event.target ), - toFocus = false; - - switch ( event.keyCode ) { - case keyCode.RIGHT: - case keyCode.DOWN: - toFocus = this.headers[ ( currentIndex + 1 ) % length ]; - break; - case keyCode.LEFT: - case keyCode.UP: - toFocus = this.headers[ ( currentIndex - 1 + length ) % length ]; - break; - case keyCode.SPACE: - case keyCode.ENTER: - this._eventHandler( event ); - break; - case keyCode.HOME: - toFocus = this.headers[ 0 ]; - break; - case keyCode.END: - toFocus = this.headers[ length - 1 ]; - break; - } - - if ( toFocus ) { - $( event.target ).attr( "tabIndex", -1 ); - $( toFocus ).attr( "tabIndex", 0 ); - toFocus.focus(); - event.preventDefault(); - } - }, - - _panelKeyDown: function( event ) { - if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) { - $( event.currentTarget ).prev().focus(); - } - }, - - refresh: function() { - var options = this.options; - this._processPanels(); - - // was collapsed or no panel - if ( ( options.active === false && options.collapsible === true ) || !this.headers.length ) { - options.active = false; - this.active = $(); - // active false only when collapsible is true - } else if ( options.active === false ) { - this._activate( 0 ); - // was active, but active panel is gone - } else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) { - // all remaining panel are disabled - if ( this.headers.length === this.headers.find(".ui-state-disabled").length ) { - options.active = false; - this.active = $(); - // activate previous panel - } else { - this._activate( Math.max( 0, options.active - 1 ) ); - } - // was active, active panel still exists - } else { - // make sure active index is correct - options.active = this.headers.index( this.active ); - } - - this._destroyIcons(); - - this._refresh(); - }, - - _processPanels: function() { - var prevHeaders = this.headers, - prevPanels = this.panels; - - this.headers = this.element.find( this.options.header ) - .addClass( "ui-accordion-header ui-state-default ui-corner-all" ); - - this.panels = this.headers.next() - .addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" ) - .filter( ":not(.ui-accordion-content-active)" ) - .hide(); - - // Avoid memory leaks (#10056) - if ( prevPanels ) { - this._off( prevHeaders.not( this.headers ) ); - this._off( prevPanels.not( this.panels ) ); - } - }, - - _refresh: function() { - var maxHeight, - options = this.options, - heightStyle = options.heightStyle, - parent = this.element.parent(); - - this.active = this._findActive( options.active ) - .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" ) - .removeClass( "ui-corner-all" ); - this.active.next() - .addClass( "ui-accordion-content-active" ) - .show(); - - this.headers - .attr( "role", "tab" ) - .each(function() { - var header = $( this ), - headerId = header.uniqueId().attr( "id" ), - panel = header.next(), - panelId = panel.uniqueId().attr( "id" ); - header.attr( "aria-controls", panelId ); - panel.attr( "aria-labelledby", headerId ); - }) - .next() - .attr( "role", "tabpanel" ); - - this.headers - .not( this.active ) - .attr({ - "aria-selected": "false", - "aria-expanded": "false", - tabIndex: -1 - }) - .next() - .attr({ - "aria-hidden": "true" - }) - .hide(); - - // make sure at least one header is in the tab order - if ( !this.active.length ) { - this.headers.eq( 0 ).attr( "tabIndex", 0 ); - } else { - this.active.attr({ - "aria-selected": "true", - "aria-expanded": "true", - tabIndex: 0 - }) - .next() - .attr({ - "aria-hidden": "false" - }); - } - - this._createIcons(); - - this._setupEvents( options.event ); - - if ( heightStyle === "fill" ) { - maxHeight = parent.height(); - this.element.siblings( ":visible" ).each(function() { - var elem = $( this ), - position = elem.css( "position" ); - - if ( position === "absolute" || position === "fixed" ) { - return; - } - maxHeight -= elem.outerHeight( true ); - }); - - this.headers.each(function() { - maxHeight -= $( this ).outerHeight( true ); - }); - - this.headers.next() - .each(function() { - $( this ).height( Math.max( 0, maxHeight - - $( this ).innerHeight() + $( this ).height() ) ); - }) - .css( "overflow", "auto" ); - } else if ( heightStyle === "auto" ) { - maxHeight = 0; - this.headers.next() - .each(function() { - maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() ); - }) - .height( maxHeight ); - } - }, - - _activate: function( index ) { - var active = this._findActive( index )[ 0 ]; - - // trying to activate the already active panel - if ( active === this.active[ 0 ] ) { - return; - } - - // trying to collapse, simulate a click on the currently active header - active = active || this.active[ 0 ]; - - this._eventHandler({ - target: active, - currentTarget: active, - preventDefault: $.noop - }); - }, - - _findActive: function( selector ) { - return typeof selector === "number" ? this.headers.eq( selector ) : $(); - }, - - _setupEvents: function( event ) { - var events = { - keydown: "_keydown" - }; - if ( event ) { - $.each( event.split( " " ), function( index, eventName ) { - events[ eventName ] = "_eventHandler"; - }); - } - - this._off( this.headers.add( this.headers.next() ) ); - this._on( this.headers, events ); - this._on( this.headers.next(), { keydown: "_panelKeyDown" }); - this._hoverable( this.headers ); - this._focusable( this.headers ); - }, - - _eventHandler: function( event ) { - var options = this.options, - active = this.active, - clicked = $( event.currentTarget ), - clickedIsActive = clicked[ 0 ] === active[ 0 ], - collapsing = clickedIsActive && options.collapsible, - toShow = collapsing ? $() : clicked.next(), - toHide = active.next(), - eventData = { - oldHeader: active, - oldPanel: toHide, - newHeader: collapsing ? $() : clicked, - newPanel: toShow - }; - - event.preventDefault(); - - if ( - // click on active header, but not collapsible - ( clickedIsActive && !options.collapsible ) || - // allow canceling activation - ( this._trigger( "beforeActivate", event, eventData ) === false ) ) { - return; - } - - options.active = collapsing ? false : this.headers.index( clicked ); - - // when the call to ._toggle() comes after the class changes - // it causes a very odd bug in IE 8 (see #6720) - this.active = clickedIsActive ? $() : clicked; - this._toggle( eventData ); - - // switch classes - // corner classes on the previously active header stay after the animation - active.removeClass( "ui-accordion-header-active ui-state-active" ); - if ( options.icons ) { - active.children( ".ui-accordion-header-icon" ) - .removeClass( options.icons.activeHeader ) - .addClass( options.icons.header ); - } - - if ( !clickedIsActive ) { - clicked - .removeClass( "ui-corner-all" ) - .addClass( "ui-accordion-header-active ui-state-active ui-corner-top" ); - if ( options.icons ) { - clicked.children( ".ui-accordion-header-icon" ) - .removeClass( options.icons.header ) - .addClass( options.icons.activeHeader ); - } - - clicked - .next() - .addClass( "ui-accordion-content-active" ); - } - }, - - _toggle: function( data ) { - var toShow = data.newPanel, - toHide = this.prevShow.length ? this.prevShow : data.oldPanel; - - // handle activating a panel during the animation for another activation - this.prevShow.add( this.prevHide ).stop( true, true ); - this.prevShow = toShow; - this.prevHide = toHide; - - if ( this.options.animate ) { - this._animate( toShow, toHide, data ); - } else { - toHide.hide(); - toShow.show(); - this._toggleComplete( data ); - } - - toHide.attr({ - "aria-hidden": "true" - }); - toHide.prev().attr({ - "aria-selected": "false", - "aria-expanded": "false" - }); - // if we're switching panels, remove the old header from the tab order - // if we're opening from collapsed state, remove the previous header from the tab order - // if we're collapsing, then keep the collapsing header in the tab order - if ( toShow.length && toHide.length ) { - toHide.prev().attr({ - "tabIndex": -1, - "aria-expanded": "false" - }); - } else if ( toShow.length ) { - this.headers.filter(function() { - return parseInt( $( this ).attr( "tabIndex" ), 10 ) === 0; - }) - .attr( "tabIndex", -1 ); - } - - toShow - .attr( "aria-hidden", "false" ) - .prev() - .attr({ - "aria-selected": "true", - "aria-expanded": "true", - tabIndex: 0 - }); - }, - - _animate: function( toShow, toHide, data ) { - var total, easing, duration, - that = this, - adjust = 0, - boxSizing = toShow.css( "box-sizing" ), - down = toShow.length && - ( !toHide.length || ( toShow.index() < toHide.index() ) ), - animate = this.options.animate || {}, - options = down && animate.down || animate, - complete = function() { - that._toggleComplete( data ); - }; - - if ( typeof options === "number" ) { - duration = options; - } - if ( typeof options === "string" ) { - easing = options; - } - // fall back from options to animation in case of partial down settings - easing = easing || options.easing || animate.easing; - duration = duration || options.duration || animate.duration; - - if ( !toHide.length ) { - return toShow.animate( this.showProps, duration, easing, complete ); - } - if ( !toShow.length ) { - return toHide.animate( this.hideProps, duration, easing, complete ); - } - - total = toShow.show().outerHeight(); - toHide.animate( this.hideProps, { - duration: duration, - easing: easing, - step: function( now, fx ) { - fx.now = Math.round( now ); - } - }); - toShow - .hide() - .animate( this.showProps, { - duration: duration, - easing: easing, - complete: complete, - step: function( now, fx ) { - fx.now = Math.round( now ); - if ( fx.prop !== "height" ) { - if ( boxSizing === "content-box" ) { - adjust += fx.now; - } - } else if ( that.options.heightStyle !== "content" ) { - fx.now = Math.round( total - toHide.outerHeight() - adjust ); - adjust = 0; - } - } - }); - }, - - _toggleComplete: function( data ) { - var toHide = data.oldPanel; - - toHide - .removeClass( "ui-accordion-content-active" ) - .prev() - .removeClass( "ui-corner-top" ) - .addClass( "ui-corner-all" ); - - // Work around for rendering bug in IE (#5421) - if ( toHide.length ) { - toHide.parent()[ 0 ].className = toHide.parent()[ 0 ].className; - } - this._trigger( "activate", null, data ); - } -}); - - -/*! - * jQuery UI Menu 1.11.4 - * http://jqueryui.com - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - * - * http://api.jqueryui.com/menu/ - */ - - -var menu = $.widget( "ui.menu", { - version: "1.11.4", - defaultElement: "
    ", - delay: 300, - options: { - icons: { - submenu: "ui-icon-carat-1-e" - }, - items: "> *", - menus: "ul", - position: { - my: "left-1 top", - at: "right top" - }, - role: "menu", - - // callbacks - blur: null, - focus: null, - select: null - }, - - _create: function() { - this.activeMenu = this.element; - - // Flag used to prevent firing of the click handler - // as the event bubbles up through nested menus - this.mouseHandled = false; - this.element - .uniqueId() - .addClass( "ui-menu ui-widget ui-widget-content" ) - .toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length ) - .attr({ - role: this.options.role, - tabIndex: 0 - }); - - if ( this.options.disabled ) { - this.element - .addClass( "ui-state-disabled" ) - .attr( "aria-disabled", "true" ); - } - - this._on({ - // Prevent focus from sticking to links inside menu after clicking - // them (focus should always stay on UL during navigation). - "mousedown .ui-menu-item": function( event ) { - event.preventDefault(); - }, - "click .ui-menu-item": function( event ) { - var target = $( event.target ); - if ( !this.mouseHandled && target.not( ".ui-state-disabled" ).length ) { - this.select( event ); - - // Only set the mouseHandled flag if the event will bubble, see #9469. - if ( !event.isPropagationStopped() ) { - this.mouseHandled = true; - } - - // Open submenu on click - if ( target.has( ".ui-menu" ).length ) { - this.expand( event ); - } else if ( !this.element.is( ":focus" ) && $( this.document[ 0 ].activeElement ).closest( ".ui-menu" ).length ) { - - // Redirect focus to the menu - this.element.trigger( "focus", [ true ] ); - - // If the active item is on the top level, let it stay active. - // Otherwise, blur the active item since it is no longer visible. - if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) { - clearTimeout( this.timer ); - } - } - } - }, - "mouseenter .ui-menu-item": function( event ) { - // Ignore mouse events while typeahead is active, see #10458. - // Prevents focusing the wrong item when typeahead causes a scroll while the mouse - // is over an item in the menu - if ( this.previousFilter ) { - return; - } - var target = $( event.currentTarget ); - // Remove ui-state-active class from siblings of the newly focused menu item - // to avoid a jump caused by adjacent elements both having a class with a border - target.siblings( ".ui-state-active" ).removeClass( "ui-state-active" ); - this.focus( event, target ); - }, - mouseleave: "collapseAll", - "mouseleave .ui-menu": "collapseAll", - focus: function( event, keepActiveItem ) { - // If there's already an active item, keep it active - // If not, activate the first item - var item = this.active || this.element.find( this.options.items ).eq( 0 ); - - if ( !keepActiveItem ) { - this.focus( event, item ); - } - }, - blur: function( event ) { - this._delay(function() { - if ( !$.contains( this.element[0], this.document[0].activeElement ) ) { - this.collapseAll( event ); - } - }); - }, - keydown: "_keydown" - }); - - this.refresh(); - - // Clicks outside of a menu collapse any open menus - this._on( this.document, { - click: function( event ) { - if ( this._closeOnDocumentClick( event ) ) { - this.collapseAll( event ); - } - - // Reset the mouseHandled flag - this.mouseHandled = false; - } - }); - }, - - _destroy: function() { - // Destroy (sub)menus - this.element - .removeAttr( "aria-activedescendant" ) - .find( ".ui-menu" ).addBack() - .removeClass( "ui-menu ui-widget ui-widget-content ui-menu-icons ui-front" ) - .removeAttr( "role" ) - .removeAttr( "tabIndex" ) - .removeAttr( "aria-labelledby" ) - .removeAttr( "aria-expanded" ) - .removeAttr( "aria-hidden" ) - .removeAttr( "aria-disabled" ) - .removeUniqueId() - .show(); - - // Destroy menu items - this.element.find( ".ui-menu-item" ) - .removeClass( "ui-menu-item" ) - .removeAttr( "role" ) - .removeAttr( "aria-disabled" ) - .removeUniqueId() - .removeClass( "ui-state-hover" ) - .removeAttr( "tabIndex" ) - .removeAttr( "role" ) - .removeAttr( "aria-haspopup" ) - .children().each( function() { - var elem = $( this ); - if ( elem.data( "ui-menu-submenu-carat" ) ) { - elem.remove(); - } - }); - - // Destroy menu dividers - this.element.find( ".ui-menu-divider" ).removeClass( "ui-menu-divider ui-widget-content" ); - }, - - _keydown: function( event ) { - var match, prev, character, skip, - preventDefault = true; - - switch ( event.keyCode ) { - case $.ui.keyCode.PAGE_UP: - this.previousPage( event ); - break; - case $.ui.keyCode.PAGE_DOWN: - this.nextPage( event ); - break; - case $.ui.keyCode.HOME: - this._move( "first", "first", event ); - break; - case $.ui.keyCode.END: - this._move( "last", "last", event ); - break; - case $.ui.keyCode.UP: - this.previous( event ); - break; - case $.ui.keyCode.DOWN: - this.next( event ); - break; - case $.ui.keyCode.LEFT: - this.collapse( event ); - break; - case $.ui.keyCode.RIGHT: - if ( this.active && !this.active.is( ".ui-state-disabled" ) ) { - this.expand( event ); - } - break; - case $.ui.keyCode.ENTER: - case $.ui.keyCode.SPACE: - this._activate( event ); - break; - case $.ui.keyCode.ESCAPE: - this.collapse( event ); - break; - default: - preventDefault = false; - prev = this.previousFilter || ""; - character = String.fromCharCode( event.keyCode ); - skip = false; - - clearTimeout( this.filterTimer ); - - if ( character === prev ) { - skip = true; - } else { - character = prev + character; - } - - match = this._filterMenuItems( character ); - match = skip && match.index( this.active.next() ) !== -1 ? - this.active.nextAll( ".ui-menu-item" ) : - match; - - // If no matches on the current filter, reset to the last character pressed - // to move down the menu to the first item that starts with that character - if ( !match.length ) { - character = String.fromCharCode( event.keyCode ); - match = this._filterMenuItems( character ); - } - - if ( match.length ) { - this.focus( event, match ); - this.previousFilter = character; - this.filterTimer = this._delay(function() { - delete this.previousFilter; - }, 1000 ); - } else { - delete this.previousFilter; - } - } - - if ( preventDefault ) { - event.preventDefault(); - } - }, - - _activate: function( event ) { - if ( !this.active.is( ".ui-state-disabled" ) ) { - if ( this.active.is( "[aria-haspopup='true']" ) ) { - this.expand( event ); - } else { - this.select( event ); - } - } - }, - - refresh: function() { - var menus, items, - that = this, - icon = this.options.icons.submenu, - submenus = this.element.find( this.options.menus ); - - this.element.toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length ); - - // Initialize nested menus - submenus.filter( ":not(.ui-menu)" ) - .addClass( "ui-menu ui-widget ui-widget-content ui-front" ) - .hide() - .attr({ - role: this.options.role, - "aria-hidden": "true", - "aria-expanded": "false" - }) - .each(function() { - var menu = $( this ), - item = menu.parent(), - submenuCarat = $( "" ) - .addClass( "ui-menu-icon ui-icon " + icon ) - .data( "ui-menu-submenu-carat", true ); - - item - .attr( "aria-haspopup", "true" ) - .prepend( submenuCarat ); - menu.attr( "aria-labelledby", item.attr( "id" ) ); - }); - - menus = submenus.add( this.element ); - items = menus.find( this.options.items ); - - // Initialize menu-items containing spaces and/or dashes only as dividers - items.not( ".ui-menu-item" ).each(function() { - var item = $( this ); - if ( that._isDivider( item ) ) { - item.addClass( "ui-widget-content ui-menu-divider" ); - } - }); - - // Don't refresh list items that are already adapted - items.not( ".ui-menu-item, .ui-menu-divider" ) - .addClass( "ui-menu-item" ) - .uniqueId() - .attr({ - tabIndex: -1, - role: this._itemRole() - }); - - // Add aria-disabled attribute to any disabled menu item - items.filter( ".ui-state-disabled" ).attr( "aria-disabled", "true" ); - - // If the active item has been removed, blur the menu - if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) { - this.blur(); - } - }, - - _itemRole: function() { - return { - menu: "menuitem", - listbox: "option" - }[ this.options.role ]; - }, - - _setOption: function( key, value ) { - if ( key === "icons" ) { - this.element.find( ".ui-menu-icon" ) - .removeClass( this.options.icons.submenu ) - .addClass( value.submenu ); - } - if ( key === "disabled" ) { - this.element - .toggleClass( "ui-state-disabled", !!value ) - .attr( "aria-disabled", value ); - } - this._super( key, value ); - }, - - focus: function( event, item ) { - var nested, focused; - this.blur( event, event && event.type === "focus" ); - - this._scrollIntoView( item ); - - this.active = item.first(); - focused = this.active.addClass( "ui-state-focus" ).removeClass( "ui-state-active" ); - // Only update aria-activedescendant if there's a role - // otherwise we assume focus is managed elsewhere - if ( this.options.role ) { - this.element.attr( "aria-activedescendant", focused.attr( "id" ) ); - } - - // Highlight active parent menu item, if any - this.active - .parent() - .closest( ".ui-menu-item" ) - .addClass( "ui-state-active" ); - - if ( event && event.type === "keydown" ) { - this._close(); - } else { - this.timer = this._delay(function() { - this._close(); - }, this.delay ); - } - - nested = item.children( ".ui-menu" ); - if ( nested.length && event && ( /^mouse/.test( event.type ) ) ) { - this._startOpening(nested); - } - this.activeMenu = item.parent(); - - this._trigger( "focus", event, { item: item } ); - }, - - _scrollIntoView: function( item ) { - var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight; - if ( this._hasScroll() ) { - borderTop = parseFloat( $.css( this.activeMenu[0], "borderTopWidth" ) ) || 0; - paddingTop = parseFloat( $.css( this.activeMenu[0], "paddingTop" ) ) || 0; - offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop; - scroll = this.activeMenu.scrollTop(); - elementHeight = this.activeMenu.height(); - itemHeight = item.outerHeight(); - - if ( offset < 0 ) { - this.activeMenu.scrollTop( scroll + offset ); - } else if ( offset + itemHeight > elementHeight ) { - this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight ); - } - } - }, - - blur: function( event, fromFocus ) { - if ( !fromFocus ) { - clearTimeout( this.timer ); - } - - if ( !this.active ) { - return; - } - - this.active.removeClass( "ui-state-focus" ); - this.active = null; - - this._trigger( "blur", event, { item: this.active } ); - }, - - _startOpening: function( submenu ) { - clearTimeout( this.timer ); - - // Don't open if already open fixes a Firefox bug that caused a .5 pixel - // shift in the submenu position when mousing over the carat icon - if ( submenu.attr( "aria-hidden" ) !== "true" ) { - return; - } - - this.timer = this._delay(function() { - this._close(); - this._open( submenu ); - }, this.delay ); - }, - - _open: function( submenu ) { - var position = $.extend({ - of: this.active - }, this.options.position ); - - clearTimeout( this.timer ); - this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) ) - .hide() - .attr( "aria-hidden", "true" ); - - submenu - .show() - .removeAttr( "aria-hidden" ) - .attr( "aria-expanded", "true" ) - .position( position ); - }, - - collapseAll: function( event, all ) { - clearTimeout( this.timer ); - this.timer = this._delay(function() { - // If we were passed an event, look for the submenu that contains the event - var currentMenu = all ? this.element : - $( event && event.target ).closest( this.element.find( ".ui-menu" ) ); - - // If we found no valid submenu ancestor, use the main menu to close all sub menus anyway - if ( !currentMenu.length ) { - currentMenu = this.element; - } - - this._close( currentMenu ); - - this.blur( event ); - this.activeMenu = currentMenu; - }, this.delay ); - }, - - // With no arguments, closes the currently active menu - if nothing is active - // it closes all menus. If passed an argument, it will search for menus BELOW - _close: function( startMenu ) { - if ( !startMenu ) { - startMenu = this.active ? this.active.parent() : this.element; - } - - startMenu - .find( ".ui-menu" ) - .hide() - .attr( "aria-hidden", "true" ) - .attr( "aria-expanded", "false" ) - .end() - .find( ".ui-state-active" ).not( ".ui-state-focus" ) - .removeClass( "ui-state-active" ); - }, - - _closeOnDocumentClick: function( event ) { - return !$( event.target ).closest( ".ui-menu" ).length; - }, - - _isDivider: function( item ) { - - // Match hyphen, em dash, en dash - return !/[^\-\u2014\u2013\s]/.test( item.text() ); - }, - - collapse: function( event ) { - var newItem = this.active && - this.active.parent().closest( ".ui-menu-item", this.element ); - if ( newItem && newItem.length ) { - this._close(); - this.focus( event, newItem ); - } - }, - - expand: function( event ) { - var newItem = this.active && - this.active - .children( ".ui-menu " ) - .find( this.options.items ) - .first(); - - if ( newItem && newItem.length ) { - this._open( newItem.parent() ); - - // Delay so Firefox will not hide activedescendant change in expanding submenu from AT - this._delay(function() { - this.focus( event, newItem ); - }); - } - }, - - next: function( event ) { - this._move( "next", "first", event ); - }, - - previous: function( event ) { - this._move( "prev", "last", event ); - }, - - isFirstItem: function() { - return this.active && !this.active.prevAll( ".ui-menu-item" ).length; - }, - - isLastItem: function() { - return this.active && !this.active.nextAll( ".ui-menu-item" ).length; - }, - - _move: function( direction, filter, event ) { - var next; - if ( this.active ) { - if ( direction === "first" || direction === "last" ) { - next = this.active - [ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" ) - .eq( -1 ); - } else { - next = this.active - [ direction + "All" ]( ".ui-menu-item" ) - .eq( 0 ); - } - } - if ( !next || !next.length || !this.active ) { - next = this.activeMenu.find( this.options.items )[ filter ](); - } - - this.focus( event, next ); - }, - - nextPage: function( event ) { - var item, base, height; - - if ( !this.active ) { - this.next( event ); - return; - } - if ( this.isLastItem() ) { - return; - } - if ( this._hasScroll() ) { - base = this.active.offset().top; - height = this.element.height(); - this.active.nextAll( ".ui-menu-item" ).each(function() { - item = $( this ); - return item.offset().top - base - height < 0; - }); - - this.focus( event, item ); - } else { - this.focus( event, this.activeMenu.find( this.options.items ) - [ !this.active ? "first" : "last" ]() ); - } - }, - - previousPage: function( event ) { - var item, base, height; - if ( !this.active ) { - this.next( event ); - return; - } - if ( this.isFirstItem() ) { - return; - } - if ( this._hasScroll() ) { - base = this.active.offset().top; - height = this.element.height(); - this.active.prevAll( ".ui-menu-item" ).each(function() { - item = $( this ); - return item.offset().top - base + height > 0; - }); - - this.focus( event, item ); - } else { - this.focus( event, this.activeMenu.find( this.options.items ).first() ); - } - }, - - _hasScroll: function() { - return this.element.outerHeight() < this.element.prop( "scrollHeight" ); - }, - - select: function( event ) { - // TODO: It should never be possible to not have an active item at this - // point, but the tests don't trigger mouseenter before click. - this.active = this.active || $( event.target ).closest( ".ui-menu-item" ); - var ui = { item: this.active }; - if ( !this.active.has( ".ui-menu" ).length ) { - this.collapseAll( event, true ); - } - this._trigger( "select", event, ui ); - }, - - _filterMenuItems: function(character) { - var escapedCharacter = character.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ), - regex = new RegExp( "^" + escapedCharacter, "i" ); - - return this.activeMenu - .find( this.options.items ) - - // Only match on items, not dividers or other content (#10571) - .filter( ".ui-menu-item" ) - .filter(function() { - return regex.test( $.trim( $( this ).text() ) ); - }); - } -}); - - -/*! - * jQuery UI Autocomplete 1.11.4 - * http://jqueryui.com - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - * - * http://api.jqueryui.com/autocomplete/ - */ - - -$.widget( "ui.autocomplete", { - version: "1.11.4", - defaultElement: "", - options: { - appendTo: null, - autoFocus: false, - delay: 300, - minLength: 1, - position: { - my: "left top", - at: "left bottom", - collision: "none" - }, - source: null, - - // callbacks - change: null, - close: null, - focus: null, - open: null, - response: null, - search: null, - select: null - }, - - requestIndex: 0, - pending: 0, - - _create: function() { - // Some browsers only repeat keydown events, not keypress events, - // so we use the suppressKeyPress flag to determine if we've already - // handled the keydown event. #7269 - // Unfortunately the code for & in keypress is the same as the up arrow, - // so we use the suppressKeyPressRepeat flag to avoid handling keypress - // events when we know the keydown event was used to modify the - // search term. #7799 - var suppressKeyPress, suppressKeyPressRepeat, suppressInput, - nodeName = this.element[ 0 ].nodeName.toLowerCase(), - isTextarea = nodeName === "textarea", - isInput = nodeName === "input"; - - this.isMultiLine = - // Textareas are always multi-line - isTextarea ? true : - // Inputs are always single-line, even if inside a contentEditable element - // IE also treats inputs as contentEditable - isInput ? false : - // All other element types are determined by whether or not they're contentEditable - this.element.prop( "isContentEditable" ); - - this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ]; - this.isNewMenu = true; - - this.element - .addClass( "ui-autocomplete-input" ) - .attr( "autocomplete", "off" ); - - this._on( this.element, { - keydown: function( event ) { - if ( this.element.prop( "readOnly" ) ) { - suppressKeyPress = true; - suppressInput = true; - suppressKeyPressRepeat = true; - return; - } - - suppressKeyPress = false; - suppressInput = false; - suppressKeyPressRepeat = false; - var keyCode = $.ui.keyCode; - switch ( event.keyCode ) { - case keyCode.PAGE_UP: - suppressKeyPress = true; - this._move( "previousPage", event ); - break; - case keyCode.PAGE_DOWN: - suppressKeyPress = true; - this._move( "nextPage", event ); - break; - case keyCode.UP: - suppressKeyPress = true; - this._keyEvent( "previous", event ); - break; - case keyCode.DOWN: - suppressKeyPress = true; - this._keyEvent( "next", event ); - break; - case keyCode.ENTER: - // when menu is open and has focus - if ( this.menu.active ) { - // #6055 - Opera still allows the keypress to occur - // which causes forms to submit - suppressKeyPress = true; - event.preventDefault(); - this.menu.select( event ); - } - break; - case keyCode.TAB: - if ( this.menu.active ) { - this.menu.select( event ); - } - break; - case keyCode.ESCAPE: - if ( this.menu.element.is( ":visible" ) ) { - if ( !this.isMultiLine ) { - this._value( this.term ); - } - this.close( event ); - // Different browsers have different default behavior for escape - // Single press can mean undo or clear - // Double press in IE means clear the whole form - event.preventDefault(); - } - break; - default: - suppressKeyPressRepeat = true; - // search timeout should be triggered before the input value is changed - this._searchTimeout( event ); - break; - } - }, - keypress: function( event ) { - if ( suppressKeyPress ) { - suppressKeyPress = false; - if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { - event.preventDefault(); - } - return; - } - if ( suppressKeyPressRepeat ) { - return; - } - - // replicate some key handlers to allow them to repeat in Firefox and Opera - var keyCode = $.ui.keyCode; - switch ( event.keyCode ) { - case keyCode.PAGE_UP: - this._move( "previousPage", event ); - break; - case keyCode.PAGE_DOWN: - this._move( "nextPage", event ); - break; - case keyCode.UP: - this._keyEvent( "previous", event ); - break; - case keyCode.DOWN: - this._keyEvent( "next", event ); - break; - } - }, - input: function( event ) { - if ( suppressInput ) { - suppressInput = false; - event.preventDefault(); - return; - } - this._searchTimeout( event ); - }, - focus: function() { - this.selectedItem = null; - this.previous = this._value(); - }, - blur: function( event ) { - if ( this.cancelBlur ) { - delete this.cancelBlur; - return; - } - - clearTimeout( this.searching ); - this.close( event ); - this._change( event ); - } - }); - - this._initSource(); - this.menu = $( "
      " ) - .addClass( "ui-autocomplete ui-front" ) - .appendTo( this._appendTo() ) - .menu({ - // disable ARIA support, the live region takes care of that - role: null - }) - .hide() - .menu( "instance" ); - - this._on( this.menu.element, { - mousedown: function( event ) { - // prevent moving focus out of the text field - event.preventDefault(); - - // IE doesn't prevent moving focus even with event.preventDefault() - // so we set a flag to know when we should ignore the blur event - this.cancelBlur = true; - this._delay(function() { - delete this.cancelBlur; - }); - - // clicking on the scrollbar causes focus to shift to the body - // but we can't detect a mouseup or a click immediately afterward - // so we have to track the next mousedown and close the menu if - // the user clicks somewhere outside of the autocomplete - var menuElement = this.menu.element[ 0 ]; - if ( !$( event.target ).closest( ".ui-menu-item" ).length ) { - this._delay(function() { - var that = this; - this.document.one( "mousedown", function( event ) { - if ( event.target !== that.element[ 0 ] && - event.target !== menuElement && - !$.contains( menuElement, event.target ) ) { - that.close(); - } - }); - }); - } - }, - menufocus: function( event, ui ) { - var label, item; - // support: Firefox - // Prevent accidental activation of menu items in Firefox (#7024 #9118) - if ( this.isNewMenu ) { - this.isNewMenu = false; - if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) { - this.menu.blur(); - - this.document.one( "mousemove", function() { - $( event.target ).trigger( event.originalEvent ); - }); - - return; - } - } - - item = ui.item.data( "ui-autocomplete-item" ); - if ( false !== this._trigger( "focus", event, { item: item } ) ) { - // use value to match what will end up in the input, if it was a key event - if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) { - this._value( item.value ); - } - } - - // Announce the value in the liveRegion - label = ui.item.attr( "aria-label" ) || item.value; - if ( label && $.trim( label ).length ) { - this.liveRegion.children().hide(); - $( "
      " ).text( label ).appendTo( this.liveRegion ); - } - }, - menuselect: function( event, ui ) { - var item = ui.item.data( "ui-autocomplete-item" ), - previous = this.previous; - - // only trigger when focus was lost (click on menu) - if ( this.element[ 0 ] !== this.document[ 0 ].activeElement ) { - this.element.focus(); - this.previous = previous; - // #6109 - IE triggers two focus events and the second - // is asynchronous, so we need to reset the previous - // term synchronously and asynchronously :-( - this._delay(function() { - this.previous = previous; - this.selectedItem = item; - }); - } - - if ( false !== this._trigger( "select", event, { item: item } ) ) { - this._value( item.value ); - } - // reset the term after the select event - // this allows custom select handling to work properly - this.term = this._value(); - - this.close( event ); - this.selectedItem = item; - } - }); - - this.liveRegion = $( "", { - role: "status", - "aria-live": "assertive", - "aria-relevant": "additions" - }) - .addClass( "ui-helper-hidden-accessible" ) - .appendTo( this.document[ 0 ].body ); - - // turning off autocomplete prevents the browser from remembering the - // value when navigating through history, so we re-enable autocomplete - // if the page is unloaded before the widget is destroyed. #7790 - this._on( this.window, { - beforeunload: function() { - this.element.removeAttr( "autocomplete" ); - } - }); - }, - - _destroy: function() { - clearTimeout( this.searching ); - this.element - .removeClass( "ui-autocomplete-input" ) - .removeAttr( "autocomplete" ); - this.menu.element.remove(); - this.liveRegion.remove(); - }, - - _setOption: function( key, value ) { - this._super( key, value ); - if ( key === "source" ) { - this._initSource(); - } - if ( key === "appendTo" ) { - this.menu.element.appendTo( this._appendTo() ); - } - if ( key === "disabled" && value && this.xhr ) { - this.xhr.abort(); - } - }, - - _appendTo: function() { - var element = this.options.appendTo; - - if ( element ) { - element = element.jquery || element.nodeType ? - $( element ) : - this.document.find( element ).eq( 0 ); - } - - if ( !element || !element[ 0 ] ) { - element = this.element.closest( ".ui-front" ); - } - - if ( !element.length ) { - element = this.document[ 0 ].body; - } - - return element; - }, - - _initSource: function() { - var array, url, - that = this; - if ( $.isArray( this.options.source ) ) { - array = this.options.source; - this.source = function( request, response ) { - response( $.ui.autocomplete.filter( array, request.term ) ); - }; - } else if ( typeof this.options.source === "string" ) { - url = this.options.source; - this.source = function( request, response ) { - if ( that.xhr ) { - that.xhr.abort(); - } - that.xhr = $.ajax({ - url: url, - data: request, - dataType: "json", - success: function( data ) { - response( data ); - }, - error: function() { - response([]); - } - }); - }; - } else { - this.source = this.options.source; - } - }, - - _searchTimeout: function( event ) { - clearTimeout( this.searching ); - this.searching = this._delay(function() { - - // Search if the value has changed, or if the user retypes the same value (see #7434) - var equalValues = this.term === this._value(), - menuVisible = this.menu.element.is( ":visible" ), - modifierKey = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey; - - if ( !equalValues || ( equalValues && !menuVisible && !modifierKey ) ) { - this.selectedItem = null; - this.search( null, event ); - } - }, this.options.delay ); - }, - - search: function( value, event ) { - value = value != null ? value : this._value(); - - // always save the actual value, not the one passed as an argument - this.term = this._value(); - - if ( value.length < this.options.minLength ) { - return this.close( event ); - } - - if ( this._trigger( "search", event ) === false ) { - return; - } - - return this._search( value ); - }, - - _search: function( value ) { - this.pending++; - this.element.addClass( "ui-autocomplete-loading" ); - this.cancelSearch = false; - - this.source( { term: value }, this._response() ); - }, - - _response: function() { - var index = ++this.requestIndex; - - return $.proxy(function( content ) { - if ( index === this.requestIndex ) { - this.__response( content ); - } - - this.pending--; - if ( !this.pending ) { - this.element.removeClass( "ui-autocomplete-loading" ); - } - }, this ); - }, - - __response: function( content ) { - if ( content ) { - content = this._normalize( content ); - } - this._trigger( "response", null, { content: content } ); - if ( !this.options.disabled && content && content.length && !this.cancelSearch ) { - this._suggest( content ); - this._trigger( "open" ); - } else { - // use ._close() instead of .close() so we don't cancel future searches - this._close(); - } - }, - - close: function( event ) { - this.cancelSearch = true; - this._close( event ); - }, - - _close: function( event ) { - if ( this.menu.element.is( ":visible" ) ) { - this.menu.element.hide(); - this.menu.blur(); - this.isNewMenu = true; - this._trigger( "close", event ); - } - }, - - _change: function( event ) { - if ( this.previous !== this._value() ) { - this._trigger( "change", event, { item: this.selectedItem } ); - } - }, - - _normalize: function( items ) { - // assume all items have the right format when the first item is complete - if ( items.length && items[ 0 ].label && items[ 0 ].value ) { - return items; - } - return $.map( items, function( item ) { - if ( typeof item === "string" ) { - return { - label: item, - value: item - }; - } - return $.extend( {}, item, { - label: item.label || item.value, - value: item.value || item.label - }); - }); - }, - - _suggest: function( items ) { - var ul = this.menu.element.empty(); - this._renderMenu( ul, items ); - this.isNewMenu = true; - this.menu.refresh(); - - // size and position menu - ul.show(); - this._resizeMenu(); - ul.position( $.extend({ - of: this.element - }, this.options.position ) ); - - if ( this.options.autoFocus ) { - this.menu.next(); - } - }, - - _resizeMenu: function() { - var ul = this.menu.element; - ul.outerWidth( Math.max( - // Firefox wraps long text (possibly a rounding bug) - // so we add 1px to avoid the wrapping (#7513) - ul.width( "" ).outerWidth() + 1, - this.element.outerWidth() - ) ); - }, - - _renderMenu: function( ul, items ) { - var that = this; - $.each( items, function( index, item ) { - that._renderItemData( ul, item ); - }); - }, - - _renderItemData: function( ul, item ) { - return this._renderItem( ul, item ).data( "ui-autocomplete-item", item ); - }, - - _renderItem: function( ul, item ) { - return $( "
    • " ).text( item.label ).appendTo( ul ); - }, - - _move: function( direction, event ) { - if ( !this.menu.element.is( ":visible" ) ) { - this.search( null, event ); - return; - } - if ( this.menu.isFirstItem() && /^previous/.test( direction ) || - this.menu.isLastItem() && /^next/.test( direction ) ) { - - if ( !this.isMultiLine ) { - this._value( this.term ); - } - - this.menu.blur(); - return; - } - this.menu[ direction ]( event ); - }, - - widget: function() { - return this.menu.element; - }, - - _value: function() { - return this.valueMethod.apply( this.element, arguments ); - }, - - _keyEvent: function( keyEvent, event ) { - if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) { - this._move( keyEvent, event ); - - // prevents moving cursor to beginning/end of the text field in some browsers - event.preventDefault(); - } - } -}); - -$.extend( $.ui.autocomplete, { - escapeRegex: function( value ) { - return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ); - }, - filter: function( array, term ) { - var matcher = new RegExp( $.ui.autocomplete.escapeRegex( term ), "i" ); - return $.grep( array, function( value ) { - return matcher.test( value.label || value.value || value ); - }); - } -}); - -// live region extension, adding a `messages` option -// NOTE: This is an experimental API. We are still investigating -// a full solution for string manipulation and internationalization. -$.widget( "ui.autocomplete", $.ui.autocomplete, { - options: { - messages: { - noResults: "No search results.", - results: function( amount ) { - return amount + ( amount > 1 ? " results are" : " result is" ) + - " available, use up and down arrow keys to navigate."; - } - } - }, - - __response: function( content ) { - var message; - this._superApply( arguments ); - if ( this.options.disabled || this.cancelSearch ) { - return; - } - if ( content && content.length ) { - message = this.options.messages.results( content.length ); - } else { - message = this.options.messages.noResults; - } - this.liveRegion.children().hide(); - $( "
      " ).text( message ).appendTo( this.liveRegion ); - } -}); - -var autocomplete = $.ui.autocomplete; - - -/*! - * jQuery UI Button 1.11.4 - * http://jqueryui.com - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - * - * http://api.jqueryui.com/button/ - */ - - -var lastActive, - baseClasses = "ui-button ui-widget ui-state-default ui-corner-all", - typeClasses = "ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only", - formResetHandler = function() { - var form = $( this ); - setTimeout(function() { - form.find( ":ui-button" ).button( "refresh" ); - }, 1 ); - }, - radioGroup = function( radio ) { - var name = radio.name, - form = radio.form, - radios = $( [] ); - if ( name ) { - name = name.replace( /'/g, "\\'" ); - if ( form ) { - radios = $( form ).find( "[name='" + name + "'][type=radio]" ); - } else { - radios = $( "[name='" + name + "'][type=radio]", radio.ownerDocument ) - .filter(function() { - return !this.form; - }); - } - } - return radios; - }; - -$.widget( "ui.button", { - version: "1.11.4", - defaultElement: "