diff --git a/IDR/resources/queries/bimber_data/idrAnchorDateSource.sql b/IDR/resources/queries/bimber_data/idrAnchorDateSource.sql new file mode 100644 index 000000000..7bdf019df --- /dev/null +++ b/IDR/resources/queries/bimber_data/idrAnchorDateSource.sql @@ -0,0 +1,21 @@ +SELECT + +Rh as subjectId, +PID0 as date, +'SIV Infection' as eventLabel, +'Hansen/IDR' as dataSource + +FROM bimber_data.subjects +WHERE PID0 IS NOT NULL + +UNION ALL + +SELECT + +Rh as subjectId, +D0 as date, +'Vaccination Start' as eventLabel, +'Hansen/IDR' as dataSource + +FROM bimber_data.subjects +WHERE D0 IS NOT NULL \ No newline at end of file diff --git a/IDR/resources/queries/bimber_data/idrOutcomeSource.sql b/IDR/resources/queries/bimber_data/idrOutcomeSource.sql new file mode 100644 index 000000000..7621857ce --- /dev/null +++ b/IDR/resources/queries/bimber_data/idrOutcomeSource.sql @@ -0,0 +1,14 @@ +SELECT + +Rh as Id, +cohortStart as date, + +CASE + WHEN contprog = 'C' THEN 'Controller' + WHEN contprog = 'P' THEN 'Progressor' +END as outcome, + +'Hansen/IDR' as dataSource + +FROM bimber_data.subjects +WHERE contprog IS NOT NULL AND contprog != '' \ No newline at end of file diff --git a/IDR/resources/queries/bimber_data/idrPvlSource.sql b/IDR/resources/queries/bimber_data/idrPvlSource.sql new file mode 100644 index 000000000..27a2d3f2f --- /dev/null +++ b/IDR/resources/queries/bimber_data/idrPvlSource.sql @@ -0,0 +1,13 @@ +SELECT +i_rh as Id, +d_PVLDate as date, +c_PVL as result, + +'Copies/mL' as units, +'Plasma' as sampleType, +'SIVmac239' as assayType, +'SIV' as target, +'Hansen/IDR' as dataSource + +FROM bimber_data.pvl +WHERE c_PVL != 'missing from box' \ No newline at end of file diff --git a/IDR/resources/queries/bimber_data/idrSampleSource.sql b/IDR/resources/queries/bimber_data/idrSampleSource.sql new file mode 100644 index 000000000..0a38d53a6 --- /dev/null +++ b/IDR/resources/queries/bimber_data/idrSampleSource.sql @@ -0,0 +1,27 @@ +SELECT + +MonkeyId as Id, +ID as sampleid, +SampleDate as date, +Tissue as sampleType, +CellCnt as quantity, + +'Hansen/IDR' as dataSource + +FROM bimber_data.ln_loc +WHERE SampleDate IS NOT NULL + +UNION ALL + +SELECT + +Rh as Id, +ID as sampleid, +SampleDate as date, +Tissue as sampleType, +null as quantity, + +'Hansen/IDR' as dataSource + +FROM bimber_data.ult_loc +WHERE SampleDate IS NOT NULL \ No newline at end of file diff --git a/SivStudies/resources/data/gender_codes.tsv b/SivStudies/resources/data/gender_codes.tsv index 07b681950..783f8712d 100644 --- a/SivStudies/resources/data/gender_codes.tsv +++ b/SivStudies/resources/data/gender_codes.tsv @@ -1,4 +1,4 @@ -v meaning origgender +value meaning origgender f Female f m Male m u Unknown \ No newline at end of file diff --git a/SivStudies/resources/data/labwork_types.tsv b/SivStudies/resources/data/labwork_types.tsv new file mode 100644 index 000000000..20a065173 --- /dev/null +++ b/SivStudies/resources/data/labwork_types.tsv @@ -0,0 +1,61 @@ +value title units sort_order category +GLUC Glucose mg/dL 1 Chemistry +BUN Blood urea nitrogen mg/dL 2 Chemistry +CREAT Creatinine mg/dL 3 Chemistry +CPK Creatine phosphokinase U/L 4 Chemistry +CHOL Cholesterol mg/dl 5 Chemistry +TRIG Triglyceride mg/dL 6 Chemistry +SGOT Serum glutamic oxaloacetic transaminase IU/L 7 Chemistry +LDL Low-Density Lipoprotein mg/dL 8 Chemistry +LDH Lactate dehydrogenase IU/L 9 Chemistry +TB Total Bilirubin mg/dL 10 Chemistry +GGT Gamma-glutamyltransferase IU/L 11 Chemistry +SGPT Serum glutamic pyruvic transaminase IU/L 12 Chemistry +TP Total Protein g/dL 13 Chemistry +ALB Albumin g/dL 14 Chemistry +ALKP Alkaline Phosphatase IU/L 15 Chemistry +CA Calcium mg/dL 16 Chemistry +PHOS Phosphorus mg/dL 17 Chemistry +FE Iron ?g/dL 18 Chemistry +K Potassium mmol/L 20 Chemistry +UA Uric Acid mg/dL 22 Chemistry +A/P Albumin/Protein Ratio ratio 999 Chemistry +B/C BUN/Creatinine Ratio ratio 999 Chemistry +CL Chloride mEq/L 999 Chemistry +FIBR Fibrinogen mg/dL 999 Chemistry +HDL High-Density Lipoprotein mg/dL 999 Chemistry +WBC White Blood Cells 10^3/uL 1 Hematology +NEUT% Neutrophils % 2 Hematology +BAND% Bands 3 Hematology +LYMPH% Lymphocytes % 4 Hematology +MONO% Monocytes % 5 Hematology +EOS% Eosinophils % 6 Hematology +BASO% Basophils % 7 Hematology +HCT Hematocrit % 8 Hematology +HGB Hemoglobin 9 Hematology +RBC Red Blood Cells 10^6/uL 10 Hematology +MCV Mean corpuscular volume fL 11 Hematology +MCH Mean corpuscular hemoglobin picograms 12 Hematology +MCHC Mean corpuscular hemoglobin concentration g/dL 13 Hematology +RETIC Reticulocytes % 14 Hematology +PLT Platelets 10^3/uL 15 Hematology +MYELO Myelocytes % 999 Hematology +MPV Mean platelet volume fl 999 Hematology +Anisocytosis Anisocytosis 999 Hematology +GLOB Globulin g/dL 999 Hematology +Hypochromic RBC Hypochromic RBC 999 Hematology +LUC Large unstained cells 999 Hematology +Macrocytic RBC Macrocytic RBC 999 Hematology +METAMYELO Metamyelocytes % 999 Hematology +Microcytic RBC Microcytic RBC 999 Hematology +NRBC Nucleated red blood cells 999 Hematology +PCV Packed cell volume % 999 Hematology +Polychromasia RBC Polychromasia RBC 999 Hematology +MPMN Neutrophils 999 Hematology +SEDRate Sedimentation rate 999 Hematology +RDW % 999 Hematology +NEUT# Neutrophil # 10^3/uL 16 Hematology +LYMPH# Lymphocyte # 10^3/uL 17 Hematology +MONO# Monocyte # 10^3/uL 18 Hematology +EOS# Eosinophil # 10^3/uL 19 Hematology +BASO# Basophil # 10^3/uL 20 Hematology diff --git a/SivStudies/resources/data/lookup_sets.tsv b/SivStudies/resources/data/lookup_sets.tsv index 8e2b94822..d831d31b4 100644 --- a/SivStudies/resources/data/lookup_sets.tsv +++ b/SivStudies/resources/data/lookup_sets.tsv @@ -6,4 +6,6 @@ dosage_units Dosage Units unit gender_codes Gender Codes geographic_origins Geographic Origins origin routes Routes route -volume_units Volume Units unit \ No newline at end of file +volume_units Volume Units unit +labwork_types Lab Tests test title +vl_sample_types Lab Tests test \ No newline at end of file diff --git a/SivStudies/resources/data/reports.tsv b/SivStudies/resources/data/reports.tsv index 4dd48af51..65f6076a1 100644 --- a/SivStudies/resources/data/reports.tsv +++ b/SivStudies/resources/data/reports.tsv @@ -1,12 +1,21 @@ -reportname category reporttype reporttitle visible containerpath schemaname queryname viewname report datefieldname todayonly queryhaslocation sort_order QCStateLabelFieldName description -activeAssignments Assignments and Groups query Active Assignments true study Assignment Active Assignments date false false qcstate/publicdata This report shows the active assignments for each animal -assignmentHistory Assignments and Groups query Assignment History true study Assignment date false false qcstate/publicdata This report shows all assignments records for the animals -activeGroups Assignments and Groups query Active Groups true study animal_group_members Active Members date false false qcstate/publicdata This report shows the active assignments for each animal -groupHistory Assignments and Groups query Group History true study animal_group_members date false false qcstate/publicdata This report shows all assignments records for the animals -microbiology Lab Results query Microbiology true study Microbiology Results date false false qcstate/publicdata -biochemistry Lab Results js Biochemistry true study bloodChemistry date false false Contains results of chemistry panels. Can be displayed either by panel, or showing reference ranges -clinPathRuns Lab Results query Lab Runs true study Clinpath Runs date false false qcstate/publicdata Contains all clinpath requests -iStat Lab Results js iStat true study iStat date false false qcstate/publicdata Contains iStat results -hematology Lab Results js Hematology true study hematology date false false Contains hematology data showing cell subsets -parasitology Lab Results query Parasitology true study Parasitology Results date false false qcstate/publicdata Contains results of parasitology testing -urinalysis Lab Results js Urinalysis true study urinalysisResults date false false Contains urinalysis results +reporttype category schemaname queryname viewname reporttitle subjectfieldname +query General study additionalDatatypes Additional Data +query Clinical study weight Weights +query Clinical study treatments Treatments/Medications +query Clinical study chemistryPivot Blood Chemistry +query Clinical study hematologyPivot CBC/Hematology +query Study Design study assignment Project/Study Assignment +query Research study flow Flow Cytometry +query Research study outcomes Study Outcomes/Phenotypes +query Research study genetics Genetic Data / MHC +query Clinical study procedures Procedures +query Research study viralLoads Viral Loads +query General study samples Samples +query Research study immunizations Immunizations +query General study flags Flags/Misc Information +query General study demographics Demographics +query General study demographics Project Summary Project Summary +query General study demographics MHC Type MHC Typing +query General study demographics Expanded Expanded Information +query Study Design studies timepointToDate Timepoints subjectId +query Study Design studies subjectAnchorDates Anchor Dates subjectId diff --git a/SivStudies/resources/data/species.tsv b/SivStudies/resources/data/species.tsv index 0e063c4cf..c071b7272 100644 --- a/SivStudies/resources/data/species.tsv +++ b/SivStudies/resources/data/species.tsv @@ -1,4 +1,4 @@ -common scientific_name id_prefix mhc_prefix blood_per_kg max_draw_pct blood_draw_interval cites_code dateDisabled +common_name scientific_name id_prefix mhc_prefix blood_per_kg max_draw_pct blood_draw_interval cites_code dateDisabled Baboon 60 0.2 30 Cotton-top Tamarin Saguinus oedipus so Saoe 60 0.2 30 Cynomolgus Macaca fascicularis cy Mafa 60 0.2 30 diff --git a/SivStudies/resources/data/vl_sample_types.tsv b/SivStudies/resources/data/vl_sample_types.tsv new file mode 100644 index 000000000..51943b0b3 --- /dev/null +++ b/SivStudies/resources/data/vl_sample_types.tsv @@ -0,0 +1,3 @@ +value +Plasma +Cells \ No newline at end of file diff --git a/SivStudies/resources/etls/idr-data.xml b/SivStudies/resources/etls/idr-data.xml new file mode 100644 index 000000000..aa9aaf086 --- /dev/null +++ b/SivStudies/resources/etls/idr-data.xml @@ -0,0 +1,162 @@ + + + Hansen/IDR + SIV Studies / Hansen/IDR Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SivStudies/resources/etls/siv-studies.xml b/SivStudies/resources/etls/siv-studies.xml index f40e3ee27..c99ffd985 100644 --- a/SivStudies/resources/etls/siv-studies.xml +++ b/SivStudies/resources/etls/siv-studies.xml @@ -37,6 +37,7 @@ + @@ -58,7 +59,7 @@ - + @@ -81,7 +82,7 @@ - + @@ -105,7 +106,7 @@ - + @@ -129,10 +130,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SivStudies/resources/folderTypes/SIV Studies.folderType.xml b/SivStudies/resources/folderTypes/SIV Studies.folderType.xml index 2b6cda35a..98951bf22 100644 --- a/SivStudies/resources/folderTypes/SIV Studies.folderType.xml +++ b/SivStudies/resources/folderTypes/SIV Studies.folderType.xml @@ -2,54 +2,38 @@ SIV Studies Overview The default folder layout for Studies - - - - - - - - - - - - - - - datasets - Datasets + overview + Overview + + + + + + + + Studies Overview + body + + + + + dataBrowser + Data Browser - datasets + dataBrowser - Datasets + Laboratory Data Browser body - - - - - - - - - - - - - - - - - admin Admin @@ -64,6 +48,10 @@ SIV Studies Admin body + + Lab Tools + right + diff --git a/SivStudies/resources/queries/laboratory/reports.js b/SivStudies/resources/queries/laboratory/reports.js new file mode 100644 index 000000000..c6f5589e9 --- /dev/null +++ b/SivStudies/resources/queries/laboratory/reports.js @@ -0,0 +1,11 @@ +function afterInsert() { + org.labkey.api.laboratory.LaboratoryService.get().clearDataProviderCache(); +} + +function afterUpdate() { + org.labkey.api.laboratory.LaboratoryService.get().clearDataProviderCache(); +} + +function afterDelete() { + org.labkey.api.laboratory.LaboratoryService.get().clearDataProviderCache(); +} \ No newline at end of file diff --git a/SivStudies/resources/queries/study/additionalDatatypes.query.xml b/SivStudies/resources/queries/study/additionalDatatypes.query.xml new file mode 100644 index 000000000..07aff7121 --- /dev/null +++ b/SivStudies/resources/queries/study/additionalDatatypes.query.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + Category + + + Data Source + + + Description + + + + + + \ No newline at end of file diff --git a/SivStudies/resources/queries/study/additionalDatatypes/.qview.xml b/SivStudies/resources/queries/study/additionalDatatypes/.qview.xml new file mode 100644 index 000000000..612c76c8a --- /dev/null +++ b/SivStudies/resources/queries/study/additionalDatatypes/.qview.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/SivStudies/resources/queries/study/assignment.query.xml b/SivStudies/resources/queries/study/assignment.query.xml index 05d40e8ca..ed31de818 100644 --- a/SivStudies/resources/queries/study/assignment.query.xml +++ b/SivStudies/resources/queries/study/assignment.query.xml @@ -5,7 +5,7 @@ - Date Added + Start Date Date @@ -21,9 +21,16 @@ Cohort ID + + Data Source + Category + + Description + true + diff --git a/SivStudies/resources/queries/study/assignment/.qview.xml b/SivStudies/resources/queries/study/assignment/.qview.xml new file mode 100644 index 000000000..b10c793d3 --- /dev/null +++ b/SivStudies/resources/queries/study/assignment/.qview.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SivStudies/resources/queries/study/chemistryPivot.query.xml b/SivStudies/resources/queries/study/chemistryPivot.query.xml new file mode 100644 index 000000000..92c8a7384 --- /dev/null +++ b/SivStudies/resources/queries/study/chemistryPivot.query.xml @@ -0,0 +1,20 @@ + + + + + Chemistry Results + + + + http://cpas.labkey.com/Study#ParticipantId + + study + animal + Id + + + + + + + \ No newline at end of file diff --git a/SivStudies/resources/queries/study/chemistryPivot.sql b/SivStudies/resources/queries/study/chemistryPivot.sql new file mode 100644 index 000000000..139f2cb9e --- /dev/null +++ b/SivStudies/resources/queries/study/chemistryPivot.sql @@ -0,0 +1,24 @@ +SELECT + b.Id, + b.date, + b.method, + b.test, + group_concat(b.result) as result + +FROM (SELECT + b.Id, + b.date, + b.test, + b.method, + CASE + WHEN b.result IS NULL THEN b.qualresult + ELSE CAST(CAST(b.result AS float) AS VARCHAR) + END as result + FROM study.labwork b + WHERE b.test.category = 'Chemistry' AND b.test.sort_order != 999 +) b + +GROUP BY b.id, b.date, b.test, b.method +PIVOT result BY test IN (select value from studies.labwork_types t WHERE t.sort_order != 999 AND t.category = 'Chemistry' order by sort_order) + + diff --git a/SivStudies/resources/queries/study/demographics.query.xml b/SivStudies/resources/queries/study/demographics.query.xml index 5c9945815..49f10a6dd 100644 --- a/SivStudies/resources/queries/study/demographics.query.xml +++ b/SivStudies/resources/queries/study/demographics.query.xml @@ -32,8 +32,8 @@ true - false true + false Sex @@ -51,14 +51,17 @@ Species - Mother + Mother/Dam - Father + Father/Sire Status + + Data Source + diff --git a/SivStudies/resources/queries/study/demographics/.qview.xml b/SivStudies/resources/queries/study/demographics/.qview.xml index 081c28720..677d864d0 100644 --- a/SivStudies/resources/queries/study/demographics/.qview.xml +++ b/SivStudies/resources/queries/study/demographics/.qview.xml @@ -12,6 +12,10 @@ + + + + diff --git a/SivStudies/resources/queries/study/demographics/Expanded.qview.xml b/SivStudies/resources/queries/study/demographics/Expanded.qview.xml new file mode 100644 index 000000000..6cfb780d6 --- /dev/null +++ b/SivStudies/resources/queries/study/demographics/Expanded.qview.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SivStudies/resources/queries/study/demographics/MHC Type.qview.xml b/SivStudies/resources/queries/study/demographics/MHC Type.qview.xml index e0bd6d743..063bf9c6a 100644 --- a/SivStudies/resources/queries/study/demographics/MHC Type.qview.xml +++ b/SivStudies/resources/queries/study/demographics/MHC Type.qview.xml @@ -7,10 +7,12 @@ + + diff --git a/SivStudies/resources/queries/study/demographics/Project Summary.qview.xml b/SivStudies/resources/queries/study/demographics/Project Summary.qview.xml new file mode 100644 index 000000000..30c79c60a --- /dev/null +++ b/SivStudies/resources/queries/study/demographics/Project Summary.qview.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SivStudies/resources/queries/study/demographicsImmunizations.query.xml b/SivStudies/resources/queries/study/demographicsImmunizations.query.xml new file mode 100644 index 000000000..20e1fe989 --- /dev/null +++ b/SivStudies/resources/queries/study/demographicsImmunizations.query.xml @@ -0,0 +1,22 @@ + + + + + Immunization Summary + + + true + true + + + Immunizations + + + Immunization Types + + + immunizations + + + + diff --git a/SivStudies/resources/queries/study/demographicsImmunizations.sql b/SivStudies/resources/queries/study/demographicsImmunizations.sql new file mode 100644 index 000000000..a31a58cee --- /dev/null +++ b/SivStudies/resources/queries/study/demographicsImmunizations.sql @@ -0,0 +1,7 @@ +SELECT + s.Id, + group_concat(DISTINCT s.treatment, char(10)) as immunizations, + group_concat(DISTINCT s.category, char(10)) as immunizationTypes, + +FROM study.immunizations s +GROUP BY s.Id \ No newline at end of file diff --git a/SivStudies/resources/queries/study/demographicsOutcomes.query.xml b/SivStudies/resources/queries/study/demographicsOutcomes.query.xml new file mode 100644 index 000000000..22acc4fac --- /dev/null +++ b/SivStudies/resources/queries/study/demographicsOutcomes.query.xml @@ -0,0 +1,19 @@ + + + + + Outcome Summary + + + true + true + + + Outcomes + + + outcomes + + + + diff --git a/SivStudies/resources/queries/study/demographicsOutcomes.sql b/SivStudies/resources/queries/study/demographicsOutcomes.sql new file mode 100644 index 000000000..22ffd948f --- /dev/null +++ b/SivStudies/resources/queries/study/demographicsOutcomes.sql @@ -0,0 +1,6 @@ +SELECT + s.Id, + group_concat(DISTINCT s.outcome, char(10)) as outcomes + +FROM study.outcomes s +GROUP BY s.Id \ No newline at end of file diff --git a/SivStudies/resources/queries/study/demographicsProjects.query.xml b/SivStudies/resources/queries/study/demographicsProjects.query.xml new file mode 100644 index 000000000..fc8f02e91 --- /dev/null +++ b/SivStudies/resources/queries/study/demographicsProjects.query.xml @@ -0,0 +1,28 @@ + + + + + Project Summary + + + true + true + + + Study Categories + + + All Studies + + + RhCMV Vaccines? + + + SIV/ART Projects? + + + categories + + + + diff --git a/SivStudies/resources/queries/study/demographicsProjects.sql b/SivStudies/resources/queries/study/demographicsProjects.sql new file mode 100644 index 000000000..e01f2a073 --- /dev/null +++ b/SivStudies/resources/queries/study/demographicsProjects.sql @@ -0,0 +1,10 @@ +SELECT + s.Id, + count(s.Id) as totalProjects, + group_concat(DISTINCT s.study, char(10)) as allStudies, + group_concat(DISTINCT s.category, char(10)) as categories, + + GROUP_CONCAT(distinct CASE WHEN s.category = 'RhCMV-Vaccines' THEN 'Yes' ELSE null END, char(10)) as rhCmvVaccines, + GROUP_CONCAT(distinct CASE WHEN s.category = 'SIV/ART' THEN 'Yes' ELSE null END, char(10)) as sivArt +FROM study.assignment s +GROUP BY s.Id \ No newline at end of file diff --git a/SivStudies/resources/queries/study/flags.query.xml b/SivStudies/resources/queries/study/flags.query.xml index 6e2b23eda..23071f2b8 100644 --- a/SivStudies/resources/queries/study/flags.query.xml +++ b/SivStudies/resources/queries/study/flags.query.xml @@ -5,7 +5,7 @@ - Date Added + Start Date Date @@ -15,6 +15,9 @@ Flag + + Data Source + diff --git a/SivStudies/resources/queries/study/flags/.qview.xml b/SivStudies/resources/queries/study/flags/.qview.xml new file mode 100644 index 000000000..91db6ed46 --- /dev/null +++ b/SivStudies/resources/queries/study/flags/.qview.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/SivStudies/resources/queries/study/flow.query.xml b/SivStudies/resources/queries/study/flow.query.xml new file mode 100644 index 000000000..63f49ed2f --- /dev/null +++ b/SivStudies/resources/queries/study/flow.query.xml @@ -0,0 +1,37 @@ + + + + + Flow Cytometry + + + + Start Date + Date + + + Sample Type + + + Assay Type + + + Population + + + Result + + + Units + + + Comments + + + Data Source + + + + + + diff --git a/SivStudies/resources/queries/study/flow/.qview.xml b/SivStudies/resources/queries/study/flow/.qview.xml new file mode 100644 index 000000000..c00809cd5 --- /dev/null +++ b/SivStudies/resources/queries/study/flow/.qview.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SivStudies/resources/queries/study/genetics.query.xml b/SivStudies/resources/queries/study/genetics.query.xml index 54b08daef..937945710 100644 --- a/SivStudies/resources/queries/study/genetics.query.xml +++ b/SivStudies/resources/queries/study/genetics.query.xml @@ -5,7 +5,7 @@ - Date Added + Date Date @@ -23,6 +23,9 @@ Score + + Data Source + diff --git a/SivStudies/resources/queries/study/genetics/.qview.xml b/SivStudies/resources/queries/study/genetics/.qview.xml new file mode 100644 index 000000000..bb737b813 --- /dev/null +++ b/SivStudies/resources/queries/study/genetics/.qview.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SivStudies/resources/queries/study/genomicsDataSource.sql b/SivStudies/resources/queries/study/genomicsDataSource.sql new file mode 100644 index 000000000..38d6b4454 --- /dev/null +++ b/SivStudies/resources/queries/study/genomicsDataSource.sql @@ -0,0 +1,16 @@ +SELECT + +s1.subjectId as Id, +s1.date, + +('Assay(s): ' || application) as description, +'ONPRC Genetics Core' as dataSource + +FROM (SELECT + s.subjectId, + coalesce(s.sampleDate, now()) as date, + GROUP_CONCAT(DISTINCT s.application, ', ') as application + + FROM "/Internal/ColonyData".sequenceanalysis.sequence_readsets s + GROUP BY s.subjectId, s.sampleDate +) s1 \ No newline at end of file diff --git a/SivStudies/resources/queries/study/hematologyPivot.query.xml b/SivStudies/resources/queries/study/hematologyPivot.query.xml new file mode 100644 index 000000000..92d4b22f6 --- /dev/null +++ b/SivStudies/resources/queries/study/hematologyPivot.query.xml @@ -0,0 +1,20 @@ + + + + + Hematology Results + + + + http://cpas.labkey.com/Study#ParticipantId + + study + animal + Id + + + + + + + \ No newline at end of file diff --git a/SivStudies/resources/queries/study/hematologyPivot.sql b/SivStudies/resources/queries/study/hematologyPivot.sql new file mode 100644 index 000000000..c15d59969 --- /dev/null +++ b/SivStudies/resources/queries/study/hematologyPivot.sql @@ -0,0 +1,24 @@ +SELECT + b.Id, + b.date, + b.method, + b.test, + group_concat(b.result) as result + +FROM (SELECT + b.Id, + b.date, + b.test, + b.method, + CASE + WHEN b.result IS NULL THEN b.qualresult + ELSE CAST(CAST(b.result AS float) AS VARCHAR) + END as result + FROM study.labwork b + WHERE b.test.category = 'Hematology' AND b.test.sort_order != 999 +) b + +GROUP BY b.id, b.date, b.test, b.method +PIVOT result BY test IN (select value from studies.labwork_types t WHERE t.sort_order != 999 AND t.category = 'Hematology' order by sort_order) + + diff --git a/SivStudies/resources/queries/study/immunizations.query.xml b/SivStudies/resources/queries/study/immunizations.query.xml index 833d0fde2..8e2c1528d 100644 --- a/SivStudies/resources/queries/study/immunizations.query.xml +++ b/SivStudies/resources/queries/study/immunizations.query.xml @@ -2,10 +2,11 @@ + Immunizations/Vaccinations - Date Added + Date Date @@ -26,6 +27,9 @@ Reason + + Data Source + diff --git a/SivStudies/resources/queries/study/immunizations/.qview.xml b/SivStudies/resources/queries/study/immunizations/.qview.xml new file mode 100644 index 000000000..8264aae36 --- /dev/null +++ b/SivStudies/resources/queries/study/immunizations/.qview.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SivStudies/resources/queries/study/labwork.query.xml b/SivStudies/resources/queries/study/labwork.query.xml index 99e85a14a..c0808ad17 100644 --- a/SivStudies/resources/queries/study/labwork.query.xml +++ b/SivStudies/resources/queries/study/labwork.query.xml @@ -13,6 +13,12 @@ Test + + studies + labwork_types + value + + Result @@ -28,6 +34,20 @@ Method + + Data Source + + + Result OOR Indicator + true + + + true + + + Result In Range? + true + diff --git a/SivStudies/resources/queries/study/labwork/.qview.xml b/SivStudies/resources/queries/study/labwork/.qview.xml new file mode 100644 index 000000000..553672473 --- /dev/null +++ b/SivStudies/resources/queries/study/labwork/.qview.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SivStudies/resources/queries/study/outcomes.query.xml b/SivStudies/resources/queries/study/outcomes.query.xml new file mode 100644 index 000000000..59e38fcac --- /dev/null +++ b/SivStudies/resources/queries/study/outcomes.query.xml @@ -0,0 +1,27 @@ + + + + + + + + Date + Date + + + Outcome/Phenotype + + + Sub-Outcome + + + Comments + + + Data Source + + + + + + diff --git a/SivStudies/resources/queries/study/outcomes/.qview.xml b/SivStudies/resources/queries/study/outcomes/.qview.xml new file mode 100644 index 000000000..546bccf39 --- /dev/null +++ b/SivStudies/resources/queries/study/outcomes/.qview.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SivStudies/resources/queries/study/procedures.query.xml b/SivStudies/resources/queries/study/procedures.query.xml index b866f8b5f..06cd4bdc6 100644 --- a/SivStudies/resources/queries/study/procedures.query.xml +++ b/SivStudies/resources/queries/study/procedures.query.xml @@ -7,7 +7,7 @@ - true + Category @@ -15,6 +15,9 @@ Procedure + + Data Source + diff --git a/SivStudies/resources/queries/study/procedures/.qview.xml b/SivStudies/resources/queries/study/procedures/.qview.xml new file mode 100644 index 000000000..01c43bfd2 --- /dev/null +++ b/SivStudies/resources/queries/study/procedures/.qview.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SivStudies/resources/queries/study/samples.query.xml b/SivStudies/resources/queries/study/samples.query.xml index 87ac54943..a65518efc 100644 --- a/SivStudies/resources/queries/study/samples.query.xml +++ b/SivStudies/resources/queries/study/samples.query.xml @@ -5,7 +5,7 @@ - Date Added + Date Date @@ -23,7 +23,9 @@ Quantity Units - + + Data Source + diff --git a/SivStudies/resources/queries/study/samples/.qview.xml b/SivStudies/resources/queries/study/samples/.qview.xml new file mode 100644 index 000000000..830bf2440 --- /dev/null +++ b/SivStudies/resources/queries/study/samples/.qview.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SivStudies/resources/queries/study/scRNAseqDataSource.sql b/SivStudies/resources/queries/study/scRNAseqDataSource.sql new file mode 100644 index 000000000..2517b709b --- /dev/null +++ b/SivStudies/resources/queries/study/scRNAseqDataSource.sql @@ -0,0 +1,20 @@ +SELECT + +s1.subjectId as Id, +s1.date, + +('Assay(s): ' || assayType || char(10) || +'Tissue(s): ' || tissue || char(10) || +'Stims(s): ' || stims || char(10)) as description, +'Bimber Lab' as dataSource + +FROM (SELECT + s.subjectId, + s.sampleDate as date, + GROUP_CONCAT(DISTINCT s.tissue, ', ') as tissue, + GROUP_CONCAT(DISTINCT s.assayType, ', ') as assayType, + GROUP_CONCAT(DISTINCT s.stim, ', ') as stims + + FROM "/Labs/Bimber".singlecell.samples s + GROUP BY s.subjectId, s.sampleDate +) s1 \ No newline at end of file diff --git a/SivStudies/resources/queries/study/studyData.query.xml b/SivStudies/resources/queries/study/studyData.query.xml index bf1d995a8..4a1d46d73 100644 --- a/SivStudies/resources/queries/study/studyData.query.xml +++ b/SivStudies/resources/queries/study/studyData.query.xml @@ -5,7 +5,7 @@ - + http://cpas.labkey.com/Study#ParticipantId Date @@ -13,12 +13,30 @@ End Date - + Key true + true false + + true + + + true + + + + Filter + + + Groups + + + Manage + + diff --git a/SivStudies/resources/queries/study/treatments.query.xml b/SivStudies/resources/queries/study/treatments.query.xml index 1a5e82c98..52a3ba764 100644 --- a/SivStudies/resources/queries/study/treatments.query.xml +++ b/SivStudies/resources/queries/study/treatments.query.xml @@ -65,6 +65,9 @@ true false + + Data Source + diff --git a/SivStudies/resources/queries/study/treatments/.qview.xml b/SivStudies/resources/queries/study/treatments/.qview.xml new file mode 100644 index 000000000..d9b12386c --- /dev/null +++ b/SivStudies/resources/queries/study/treatments/.qview.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SivStudies/resources/queries/study/viralLoads/.qview.xml b/SivStudies/resources/queries/study/viralLoads/.qview.xml new file mode 100644 index 000000000..ef089d8f7 --- /dev/null +++ b/SivStudies/resources/queries/study/viralLoads/.qview.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SivStudies/resources/queries/study/viralloads.query.xml b/SivStudies/resources/queries/study/viralloads.query.xml index ef00f0e25..4e9e9af40 100644 --- a/SivStudies/resources/queries/study/viralloads.query.xml +++ b/SivStudies/resources/queries/study/viralloads.query.xml @@ -10,18 +10,26 @@ Sample Type + false + + studies + vl_sample_types + value + + Assay Type - target + Target LOD Result + false Units @@ -31,9 +39,19 @@ true false + + Data Source + + Result OOR Indicator + true + + + true + + + Result In Range? true - false diff --git a/SivStudies/resources/queries/study/weight.query.xml b/SivStudies/resources/queries/study/weight.query.xml index 24e484dd7..dde79d9ad 100644 --- a/SivStudies/resources/queries/study/weight.query.xml +++ b/SivStudies/resources/queries/study/weight.query.xml @@ -13,6 +13,9 @@ Weight (kg) 0.#### + + Data Source + diff --git a/SivStudies/resources/queries/study/weight/.qview.xml b/SivStudies/resources/queries/study/weight/.qview.xml new file mode 100644 index 000000000..d329de569 --- /dev/null +++ b/SivStudies/resources/queries/study/weight/.qview.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SivStudies/resources/referenceStudy/study/datasets/datasets_manifest.xml b/SivStudies/resources/referenceStudy/study/datasets/datasets_manifest.xml index 346bd0300..715134ec4 100644 --- a/SivStudies/resources/referenceStudy/study/datasets/datasets_manifest.xml +++ b/SivStudies/resources/referenceStudy/study/datasets/datasets_manifest.xml @@ -37,5 +37,14 @@ + + + + + + + + + diff --git a/SivStudies/resources/referenceStudy/study/datasets/datasets_metadata.xml b/SivStudies/resources/referenceStudy/study/datasets/datasets_metadata.xml index 24842c6ca..47f66507d 100644 --- a/SivStudies/resources/referenceStudy/study/datasets/datasets_metadata.xml +++ b/SivStudies/resources/referenceStudy/study/datasets/datasets_metadata.xml @@ -10,7 +10,10 @@ timestamp http://cpas.labkey.com/laboratory#sampleDate - + + varchar + + entityid true @@ -34,7 +37,10 @@ timestamp http://cpas.labkey.com/laboratory#sampleDate - + + varchar + + entityid true @@ -94,7 +100,10 @@ timestamp http://cpas.labkey.com/laboratory#sampleDate - + + varchar + + entityid true @@ -130,7 +139,10 @@ timestamp http://cpas.labkey.com/laboratory#sampleDate - + + varchar + + entityid true @@ -151,7 +163,10 @@ timestamp http://cpas.labkey.com/laboratory#sampleDate - + + varchar + + entityid true @@ -175,7 +190,7 @@ varchar true - org.labkey.studies.query.ResultsOOODisplayColumn + org.labkey.api.studies.query.ResultsOORDisplayColumn @@ -197,7 +212,10 @@ timestamp http://cpas.labkey.com/laboratory#sampleDate - + + varchar + + entityid true @@ -235,7 +253,10 @@ timestamp http://cpas.labkey.com/laboratory#sampleDate - + + varchar + + entityid false @@ -277,7 +298,10 @@ timestamp http://cpas.labkey.com/laboratory#sampleDate - + + varchar + + entityid true @@ -313,7 +337,10 @@ timestamp http://cpas.labkey.com/laboratory#sampleDate - + + varchar + + entityid true @@ -346,7 +373,10 @@ timestamp http://cpas.labkey.com/laboratory#sampleDate - + + varchar + + entityid true @@ -378,7 +408,10 @@ timestamp http://cpas.labkey.com/laboratory#sampleDate - + + varchar + + entityid true @@ -391,4 +424,97 @@ Procedures + + + + varchar + http://cpas.labkey.com/Study#ParticipantId + + + timestamp + http://cpas.labkey.com/laboratory#sampleDate + + + varchar + + + entityid + true + + + varchar + + + varchar + + + Additional Datatypes + + + + + varchar + http://cpas.labkey.com/Study#ParticipantId + + + timestamp + http://cpas.labkey.com/laboratory#sampleDate + + + varchar + + + entityid + true + + + varchar + + + varchar + + + varchar + + + double + + + varchar + + + varchar + + + Flow Cytometry + + + + + varchar + http://cpas.labkey.com/Study#ParticipantId + + + timestamp + http://cpas.labkey.com/laboratory#sampleDate + + + varchar + + + entityid + true + + + varchar + + + varchar + + + varchar + + + Outcomes/Phenotypes + diff --git a/SivStudies/src/org/labkey/sivstudies/SivStudiesModule.java b/SivStudies/src/org/labkey/sivstudies/SivStudiesModule.java index 020e98897..4bedeb33e 100644 --- a/SivStudies/src/org/labkey/sivstudies/SivStudiesModule.java +++ b/SivStudies/src/org/labkey/sivstudies/SivStudiesModule.java @@ -21,6 +21,9 @@ import org.labkey.api.data.Container; import org.labkey.api.ldk.ExtendedSimpleModule; import org.labkey.api.module.ModuleContext; +import org.labkey.api.studies.StudiesService; +import org.labkey.sivstudies.study.ArtInitiationEventProvider; +import org.labkey.sivstudies.study.SivInfectionEventProvider; import java.util.Collection; import java.util.Collections; @@ -51,7 +54,8 @@ protected void init() @Override public void doStartupAfterSpringConfig(ModuleContext moduleContext) { - + StudiesService.get().registerEventProvider(new SivInfectionEventProvider()); + StudiesService.get().registerEventProvider(new ArtInitiationEventProvider()); } @Override diff --git a/SivStudies/src/org/labkey/sivstudies/etl/SubjectScopedSelect.java b/SivStudies/src/org/labkey/sivstudies/etl/SubjectScopedSelect.java index c2589a219..a36369cea 100644 --- a/SivStudies/src/org/labkey/sivstudies/etl/SubjectScopedSelect.java +++ b/SivStudies/src/org/labkey/sivstudies/etl/SubjectScopedSelect.java @@ -12,8 +12,10 @@ import org.labkey.api.data.SimpleFilter; import org.labkey.api.data.TableInfo; import org.labkey.api.data.TableSelector; +import org.labkey.api.dataiterator.DetailedAuditLogDataIterator; import org.labkey.api.di.DataIntegrationService; import org.labkey.api.di.TaskRefTask; +import org.labkey.api.pipeline.CancelledException; import org.labkey.api.pipeline.PipelineJob; import org.labkey.api.pipeline.PipelineJobException; import org.labkey.api.pipeline.RecordedActionSet; @@ -49,6 +51,8 @@ import java.util.Set; import java.util.stream.Collectors; +import static org.labkey.api.gwt.client.AuditBehaviorType.NONE; + public class SubjectScopedSelect implements TaskRefTask { protected final Map _settings = new CaseInsensitiveHashMap<>(); @@ -115,12 +119,21 @@ public RecordedActionSet run(@NotNull PipelineJob job) throws PipelineJobExcepti List subjects = getSubjects(job.getLogger()); List> batches = Lists.partition(subjects, BATCH_SIZE); job.getLogger().info("Total batches: " + batches.size()); - batches.forEach(x -> processBatch(x, job.getLogger())); + batches.forEach(x -> processBatch(x, job.getLogger(), job)); return new RecordedActionSet(); } - private void processBatch(List subjects, Logger log) + private void checkCancelled(PipelineJob job) + { + job.updateStatusForTask(); + if (job.isCancelled()) + { + throw new CancelledException(); + } + } + + private void processBatch(List subjects, Logger log, PipelineJob job) { log.info("processing batch with " + subjects.size() + " subjects"); TableInfo destinationTable = getDataDestinationTable(); @@ -137,7 +150,7 @@ private void processBatch(List subjects, Logger log) final SimpleFilter subjectFilter = new SimpleFilter(FieldKey.fromString(_settings.get(Settings.targetSubjectColumn.name())), subjects, CompareType.IN); if (_settings.get(Settings.targetAdditionalFilters.name()) != null) { - List additionalFilters = parseAdditionalFilters(_settings.get(Settings.targetAdditionalFilters.name())); + List additionalFilters = parseAdditionalFilters(_settings.get(Settings.targetAdditionalFilters.name())); additionalFilters.forEach(subjectFilter::addCondition); } @@ -146,11 +159,20 @@ private void processBatch(List subjects, Logger log) throw new IllegalStateException("Unknown column on table " + destinationTable.getName() + ": " + _settings.get(Settings.targetSubjectColumn.name())); } - Collection> existingRows = new TableSelector(destinationTable, keyFields, subjectFilter, null).getMapCollection(); + List> existingRows = new ArrayList<>(new TableSelector(destinationTable, keyFields, subjectFilter, null).getMapCollection()); if (!existingRows.isEmpty()) { - log.info("deleting " + existingRows.size() + " rows"); - qus.deleteRows(_containerUser.getUser(), _containerUser.getContainer(), new ArrayList<>(existingRows), null, null); + List>> batches = Lists.partition(existingRows, 5000); + log.info("deleting " + existingRows.size() + " rows in " + batches.size() + " batches"); + int i = 0; + for (List> batch : batches) + { + i++; + log.info("batch " + i); + checkCancelled(job); + + qus.deleteRows(_containerUser.getUser(), _containerUser.getContainer(), batch, new HashMap<>(Map.of(DetailedAuditLogDataIterator.AuditConfigs.AuditBehavior, NONE, QueryUpdateService.ConfigParameters.BulkLoad, true)), null); + } } else { @@ -168,37 +190,58 @@ private void processBatch(List subjects, Logger log) { if (getMode() == MODE.TRUNCATE) { - log.info("inserting " + toImportOrUpdate.size() + " rows"); - BatchValidationException bve = new BatchValidationException(); - qus.insertRows(_containerUser.getUser(), _containerUser.getContainer(), toImportOrUpdate, bve, null, null); - if (bve.hasErrors()) + List>> batches = Lists.partition(toImportOrUpdate, 5000); + log.info("inserting " + toImportOrUpdate.size() + " rows in " + batches.size() + " batches"); + + int i = 0; + for (List> batch : batches) { - throw bve; + i++; + log.info("batch " + i); + checkCancelled(job); + + BatchValidationException bve = new BatchValidationException(); + qus.insertRows(_containerUser.getUser(), _containerUser.getContainer(), batch, bve, new HashMap<>(Map.of(DetailedAuditLogDataIterator.AuditConfigs.AuditBehavior, NONE, QueryUpdateService.ConfigParameters.BulkLoad, true)), null); + if (bve.hasErrors()) + { + throw bve; + } } } else if (getMode() == MODE.UPDATE_ONLY) { - log.info("updating " + toImportOrUpdate.size() + " rows"); - BatchValidationException bve = new BatchValidationException(); + List>> batches = Lists.partition(toImportOrUpdate, 5000); + log.info("updating " + toImportOrUpdate.size() + " rows in " + batches.size() + " batches"); - Collection keyFields = destinationTable.getPkColumnNames(); - List> keys = toImportOrUpdate.stream().map(x -> { - Map map = new HashMap<>(); - for (String keyField : keyFields) - { - if (x.get(keyField) != null) + int i = 0; + for (List> batch : batches) + { + + i++; + log.info("batch " + i); + checkCancelled(job); + + BatchValidationException bve = new BatchValidationException(); + + Collection keyFields = destinationTable.getPkColumnNames(); + List> keys = batch.stream().map(x -> { + Map map = new HashMap<>(); + for (String keyField : keyFields) { - map.put(keyField, x.get(keyField)); + if (x.get(keyField) != null) + { + map.put(keyField, x.get(keyField)); + } } - } - return map; - }).toList(); + return map; + }).toList(); - qus.updateRows(_containerUser.getUser(), _containerUser.getContainer(), toImportOrUpdate, keys, bve, null, null); - if (bve.hasErrors()) - { - throw bve; + qus.updateRows(_containerUser.getUser(), _containerUser.getContainer(), batch, keys, bve, new HashMap<>(Map.of(DetailedAuditLogDataIterator.AuditConfigs.AuditBehavior, NONE, QueryUpdateService.ConfigParameters.BulkLoad, true)), null); + if (bve.hasErrors()) + { + throw bve; + } } } else @@ -217,7 +260,7 @@ else if (getMode() == MODE.UPDATE_ONLY) } } - private List parseAdditionalFilters(String rawVal) + private List parseAdditionalFilters(String rawVal) { rawVal = StringUtils.trimToNull(rawVal); if (rawVal == null) @@ -233,12 +276,12 @@ private List parseAdditionalFilters(String rawVal) } return filter.getClauses().stream().map(fc -> { - if (fc instanceof CompareType.CompareClause cc) + if (fc instanceof CompareType.AbstractCompareClause cc) { return cc; } - throw new IllegalStateException("Expected all filters to be instance CompareType.CompareClause, found: " + fc.getClass()); + throw new IllegalStateException("Expected all filters to be instance CompareType.AbstractCompareClause, found: " + fc.getClass()); }).toList(); } @@ -315,14 +358,28 @@ private List> getRowsToImport(List subjects, Logger if (_settings.get(Settings.dataRemoteSource.name()) != null) { - DataIntegrationService.RemoteConnection rc = getRemoteDataSource(_settings.get(Settings.dataRemoteSource.name()), log); + Container target; + if (_settings.get(Settings.dataSourceContainerPath.name()) != null) + { + target = ContainerManager.getForPath(_settings.get(Settings.dataSourceContainerPath.name())); + if (target == null) + { + throw new IllegalStateException("Unknown container: " + _settings.get(Settings.dataSourceContainerPath.name())); + } + } + else + { + target =_containerUser.getContainer(); + } + + DataIntegrationService.RemoteConnection rc = getRemoteDataSource(_settings.get(Settings.dataRemoteSource.name()), target, log); SelectRowsCommand sr = new SelectRowsCommand(_settings.get(Settings.dataSourceSchema.name()), _settings.get(Settings.dataSourceQuery.name())); sr.setColumns(sourceColumns); sr.addFilter(_settings.get(Settings.dataSourceSubjectColumn.name()), StringUtils.join(subjects, ";"), Filter.Operator.IN); if (_settings.get(Settings.dataSourceAdditionalFilters.name()) != null) { - List additionalFilters = parseAdditionalFilters(_settings.get(Settings.dataSourceAdditionalFilters.name())); - for (CompareType.CompareClause f : additionalFilters) + List additionalFilters = parseAdditionalFilters(_settings.get(Settings.dataSourceAdditionalFilters.name())); + for (CompareType.AbstractCompareClause f : additionalFilters) { Object value; if (f.getParamVals() == null) @@ -361,11 +418,16 @@ else if (f.getParamVals().length == 1) } else { + Container source; if (_settings.get(Settings.dataSourceContainerPath.name()) == null) { - throw new IllegalStateException("Must provide dataSourceContainerPath for local sources"); + source = _containerUser.getContainer(); } - Container source = ContainerManager.getForPath(_settings.get(Settings.dataSourceContainerPath.name())); + else + { + source = ContainerManager.getForPath(_settings.get(Settings.dataSourceContainerPath.name())); + } + if (source == null) { throw new IllegalStateException("Unknown container: " + _settings.get(Settings.dataSourceContainerPath.name())); @@ -409,7 +471,7 @@ else if (f.getParamVals().length == 1) final SimpleFilter filter = new SimpleFilter(FieldKey.fromString(_settings.get(Settings.dataSourceSubjectColumn.name())), subjects, CompareType.IN); if (_settings.get(Settings.dataSourceAdditionalFilters.name()) != null) { - List additionalFilters = parseAdditionalFilters(_settings.get(Settings.dataSourceAdditionalFilters.name())); + List additionalFilters = parseAdditionalFilters(_settings.get(Settings.dataSourceAdditionalFilters.name())); additionalFilters.forEach(filter::addCondition); } @@ -504,9 +566,9 @@ public void setSettings(Map settings) _settings.putAll(settings); } - private DataIntegrationService.RemoteConnection getRemoteDataSource(String name, Logger log) throws IllegalStateException + private DataIntegrationService.RemoteConnection getRemoteDataSource(String name, Container c, Logger log) throws IllegalStateException { - DataIntegrationService.RemoteConnection rc = DataIntegrationService.get().getRemoteConnection(name, _containerUser.getContainer(), log); + DataIntegrationService.RemoteConnection rc = DataIntegrationService.get().getRemoteConnection(name, c, log); if (rc == null) { throw new IllegalStateException("Unable to find remote connection: " + name); @@ -519,7 +581,21 @@ private List getSubjects(Logger log) { if (_settings.get(Settings.subjectRemoteSource.name()) != null) { - DataIntegrationService.RemoteConnection rc = getRemoteDataSource(_settings.get(Settings.subjectRemoteSource.name()), log); + Container target; + if (_settings.get(Settings.subjectSourceContainerPath.name()) != null) + { + target = ContainerManager.getForPath(_settings.get(Settings.subjectSourceContainerPath.name())); + if (target == null) + { + throw new IllegalStateException("Unknown container: " + _settings.get(Settings.subjectSourceContainerPath.name())); + } + } + else + { + target =_containerUser.getContainer(); + } + + DataIntegrationService.RemoteConnection rc = getRemoteDataSource(_settings.get(Settings.subjectRemoteSource.name()), target, log); SelectRowsCommand sr = new SelectRowsCommand(_settings.get(Settings.subjectSourceSchema.name()), _settings.get(Settings.subjectSourceQuery.name())); sr.setColumns(Arrays.asList(_settings.get(Settings.subjectSourceColumn.name()))); diff --git a/SivStudies/src/org/labkey/sivstudies/query/SivStudiesCustomizer.java b/SivStudies/src/org/labkey/sivstudies/query/SivStudiesCustomizer.java index b1b4de83e..48b615b8a 100644 --- a/SivStudies/src/org/labkey/sivstudies/query/SivStudiesCustomizer.java +++ b/SivStudies/src/org/labkey/sivstudies/query/SivStudiesCustomizer.java @@ -1,13 +1,17 @@ package org.labkey.sivstudies.query; import org.apache.logging.log4j.Logger; +import org.labkey.api.collections.CaseInsensitiveHashSet; import org.labkey.api.data.AbstractTableInfo; import org.labkey.api.data.BaseColumnInfo; import org.labkey.api.data.ColumnInfo; import org.labkey.api.data.Container; +import org.labkey.api.data.JdbcType; +import org.labkey.api.data.SQLFragment; import org.labkey.api.data.TableInfo; import org.labkey.api.data.WrappedColumn; import org.labkey.api.ldk.table.AbstractTableCustomizer; +import org.labkey.api.query.ExprColumn; import org.labkey.api.query.LookupForeignKey; import org.labkey.api.query.QueryDefinition; import org.labkey.api.query.QueryException; @@ -15,24 +19,38 @@ import org.labkey.api.query.QueryService; import org.labkey.api.query.UserSchema; import org.labkey.api.security.User; +import org.labkey.api.studies.StudiesService; +import org.labkey.api.studies.query.ResultsOORDisplayColumn; +import org.labkey.api.study.Dataset; import org.labkey.api.study.DatasetTable; import org.labkey.api.util.logging.LogHelper; import java.util.ArrayList; import java.util.List; +import java.util.Set; public class SivStudiesCustomizer extends AbstractTableCustomizer { - public static final String ID_COL = "Id"; private static final Logger _log = LogHelper.getLogger(SivStudiesCustomizer.class, "Table customization for the SIV Studies module"); + public static final String ID_COL = "Id"; + public static final String DATE_COL = "Date"; + @Override public void customize(TableInfo tableInfo) { + StudiesService.get().getStudiesTableCustomizer().customize(tableInfo); if (tableInfo instanceof DatasetTable ds) { performDatasetCustomization(ds); } + else if ("chemistryPivot".equalsIgnoreCase(tableInfo.getName()) | "hematologyPivot".equalsIgnoreCase(tableInfo.getName())) + { + if (tableInfo instanceof AbstractTableInfo ati) + { + appendDemographicsColumns(ati); + } + } } public void performDatasetCustomization(DatasetTable ds) @@ -43,12 +61,17 @@ public void performDatasetCustomization(DatasetTable ds) if (!ds.getDataset().isDemographicData()) { - appendAgeAtTimeCol(ds.getUserSchema(), ati, "date"); + appendAgeAtTimeCol(ds.getUserSchema(), ati, ID_COL, DATE_COL); + appendPvlColumns(ds, ID_COL, DATE_COL); + appendSivChallengeColumns(ati, ID_COL, DATE_COL); + appendArtColumns(ds, ID_COL, DATE_COL); } - if ("demographics".equalsIgnoreCase(ds.getName())) + appendDemographicsColumns(ati); + + if ("viralLoads".equalsIgnoreCase(ds.getName())) { - appendDemographicsColumns(ati); + customizeViralLoads(ati); } } else @@ -59,11 +82,32 @@ public void performDatasetCustomization(DatasetTable ds) private ColumnInfo getPkCol(TableInfo ti) { - List pks = ti.getPkColumns(); - return (pks.size() != 1) ? null : pks.get(0); + Set pks = new CaseInsensitiveHashSet(ti.getPkColumnNames()); + if (pks.size() == 1) + { + return ti.getColumn(pks.iterator().next()); + } + else if (pks.contains("lsid")) + { + return ti.getColumn("lsid"); + } + else if (pks.contains("objectId")) + { + return ti.getColumn("objectId"); + } + else if (pks.contains("Id")) + { + return ti.getColumn("Id"); + } + else if (pks.contains("subjectId")) + { + return ti.getColumn("subjectId"); + } + + return null; } - private void appendAgeAtTimeCol(UserSchema demographicsSchema, AbstractTableInfo ds, final String dateColName) + private void appendAgeAtTimeCol(UserSchema demographicsSchema, AbstractTableInfo ds, final String subjectColName, final String dateColName) { String name = "ageAtTime"; if (ds.getColumn(name, false) != null) @@ -73,7 +117,7 @@ private void appendAgeAtTimeCol(UserSchema demographicsSchema, AbstractTableInfo if (pkCol == null) return; - final ColumnInfo idCol = ds.getColumn(ID_COL); + final ColumnInfo idCol = ds.getColumn(subjectColName); if (idCol == null) return; @@ -88,7 +132,7 @@ private void appendAgeAtTimeCol(UserSchema demographicsSchema, AbstractTableInfo final String demographicsPath = demographicsSchema.getContainer().getPath(); WrappedColumn col = new WrappedColumn(pkCol, name); - col.setLabel("Age At The Time"); + col.setLabel("Age At Time"); col.setReadOnly(true); col.setIsUnselectable(true); col.setUserEditable(false); @@ -113,16 +157,6 @@ public TableInfo getLookupTableInfo() "END AS float) as AgeAtTime,\n" + "\n" + - "CAST(\n" + - "CASE\n" + - "WHEN d.birth is null or c." + dateColName + " is null\n" + - " THEN null\n" + - "WHEN (d.death IS NOT NULL AND d.death < c." + dateColName + ") THEN\n" + - " ROUND(CONVERT(timestampdiff('SQL_TSI_DAY', d.birth, d.death), DOUBLE) / 365.25, 2)\n" + - "ELSE\n" + - " ROUND(CONVERT(timestampdiff('SQL_TSI_DAY', d.birth, CAST(c." + dateColName + " as DATE)), DOUBLE) / 365.25, 2)\n" + - "END AS float) as AgeAtTimeYears,\n" + - "\n" + "CAST(\n" + "CASE\n" + "WHEN d.birth is null or c." + dateColName + " is null\n" + @@ -172,6 +206,11 @@ public TableInfo getLookupTableInfo() { ((BaseColumnInfo)ti.getColumn(pkCol.getName())).setHidden(true); ((BaseColumnInfo)ti.getColumn(pkCol.getName())).setKeyField(true); + + ((BaseColumnInfo)ti.getColumn("AgeAtTime")).setLabel("Age At Time (Years)"); + ((BaseColumnInfo)ti.getColumn("AgeAtTimeDays")).setLabel("Age At Time (Days)"); + ((BaseColumnInfo)ti.getColumn("AgeAtTimeMonths")).setLabel("Age At Time (Months)"); + ((BaseColumnInfo)ti.getColumn("AgeAtTimeYearsRounded")).setLabel("Age At Time (Years, Rounded)"); } return ti; @@ -181,34 +220,255 @@ public TableInfo getLookupTableInfo() ds.addColumn(col); } - private void appendDemographicsColumns(AbstractTableInfo demographicsTable) + private void appendDemographicsColumns(AbstractTableInfo parentTable) { - if (demographicsTable.getColumn("mhcGenotypes") == null) + if (parentTable.getColumn("mhcGenotypes") == null) { - BaseColumnInfo colInfo = getWrappedIdCol(demographicsTable.getUserSchema(), "demographicsMHC", demographicsTable, "mhcGenotypes"); + BaseColumnInfo colInfo = getWrappedIdCol(parentTable.getUserSchema(), "demographicsMHC", parentTable, "mhcGenotypes"); colInfo.setLabel("MHC Genotypes"); - demographicsTable.addColumn(colInfo); + parentTable.addColumn(colInfo); + } + + if (parentTable.getColumn("projects") == null) + { + BaseColumnInfo colInfo = getWrappedIdCol(parentTable.getUserSchema(), "demographicsProjects", parentTable, "projects"); + colInfo.setLabel("Project Summary"); + parentTable.addColumn(colInfo); + } + + if (parentTable.getColumn("immunizations") == null) + { + BaseColumnInfo colInfo = getWrappedIdCol(parentTable.getUserSchema(), "demographicsImmunizations", parentTable, "immunizations"); + colInfo.setLabel("Immunization Summary"); + parentTable.addColumn(colInfo); + } + + if (parentTable.getColumn("outcomes") == null) + { + BaseColumnInfo colInfo = getWrappedIdCol(parentTable.getUserSchema(), "demographicsOutcomes", parentTable, "outcomes"); + colInfo.setLabel("Outcomes"); + parentTable.addColumn(colInfo); } } - private void appendPvlColumns(AbstractTableInfo ti, ColumnInfo subjectCol, ColumnInfo dateCol) + private void appendPvlColumns(DatasetTable ds, String subjectColName, String dateColName) { - Container target = ti.getUserSchema().getContainer().isWorkbookOrTab() ? ti.getUserSchema().getContainer().getParent() : ti.getUserSchema().getContainer(); - -// TableInfo data = schema.createDataTable(null); -// String tableName = data.getDomain().getStorageTableName(); -// -// String name = "viralLoad"; -// if (ti.getColumn(name) == null) -// { -// SQLFragment sql = new SQLFragment("(SELECT avg(viralLoad) as expr FROM assayresult." + tableName + " t WHERE t.subjectId = " + ExprColumn.STR_TABLE_ALIAS + "." + subjectCol.getName() + " AND CAST(t.date AS DATE) = CAST(" + ExprColumn.STR_TABLE_ALIAS + "." + dateCol.getName() + " AS DATE))"); -// ExprColumn newCol = new ExprColumn(ti, name, sql, JdbcType.DOUBLE, subjectCol, dateCol); -// newCol.setDescription("Displays the viral load from this timepoint, if present"); -// newCol.setLabel("Viral Load (copies/mL)"); -// ti.addColumn(newCol); -// } + final String name = "viralLoad"; + if (ds.getColumn(name) != null) + { + return; + } + + Dataset vl = ds.getDataset().getStudy().getDatasetByName("viralloads"); + if (vl == null) + { + return; + } + + if (ds instanceof AbstractTableInfo ti) + { + ColumnInfo subjectCol = ti.getColumn(subjectColName); + ColumnInfo dateCol = ti.getColumn(dateColName); + + final String tableName = vl.getDomain().getStorageTableName(); + SQLFragment sql = new SQLFragment("(SELECT CASE WHEN count(t.result) = 1 THEN max(t.result) ELSE null END as expr FROM studydataset." + tableName + " t WHERE t.participantid = " + ExprColumn.STR_TABLE_ALIAS + ".participantid AND CAST(t.date AS DATE) = CAST(" + ExprColumn.STR_TABLE_ALIAS + ".date AS DATE) AND t.sampletype = 'Plasma' AND t.target = 'SIV')"); + ExprColumn newCol = new ExprColumn(ti, name, sql, JdbcType.DOUBLE, subjectCol, dateCol); + newCol.setDescription("Displays the viral load from this timepoint, if present"); + newCol.setLabel("SIV PVL (copies/mL)"); + newCol.setDisplayColumnFactory(ResultsOORDisplayColumn::new); + ti.addColumn(newCol); + + String nameOOR = name + "OORIndicator"; + SQLFragment sqlOOR = new SQLFragment("(SELECT CASE WHEN count(t.result) = 1 THEN max(t.resultOORIndicator) ELSE null END as expr FROM studydataset." + tableName + " t WHERE t.participantid = " + ExprColumn.STR_TABLE_ALIAS + ".participantid AND CAST(t.date AS DATE) = CAST(" + ExprColumn.STR_TABLE_ALIAS + ".date AS DATE) AND t.sampletype = 'Plasma' AND t.target = 'SIV')"); + ExprColumn newColOOR = new ExprColumn(ti, nameOOR, sqlOOR, JdbcType.VARCHAR, subjectCol, dateCol); + newColOOR.setDescription("For the corresponding PVL, this indicates if the value is out-of-range"); + newColOOR.setLabel("SIV PVL OOR Indicator"); + newColOOR.setHidden(true); + + ti.addColumn(newColOOR); + } } + private void appendSivChallengeColumns(AbstractTableInfo targetTable, String subjectColName, String dateColName) + { + String name = "timePostSivChallenge"; + if (targetTable.getColumn(name, false) != null) + return; + + final ColumnInfo pkCol = getPkCol(targetTable); + if (pkCol == null) + return; + + final ColumnInfo idCol = targetTable.getColumn(subjectColName); + if (idCol == null) + return; + + final ColumnInfo dateCol = targetTable.getColumn(dateColName); + if (dateCol == null) + return; + + final String targetSchemaName = targetTable.getUserSchema().getName(); + final Container targetSchemaContainer = targetTable.getUserSchema().getContainer(); + final User u = targetTable.getUserSchema().getUser(); + final String schemaName = targetTable.getPublicSchemaName(); + final String queryName = targetTable.getName(); + + WrappedColumn col = new WrappedColumn(pkCol, name); + col.setLabel("SIV Challenge"); + col.setReadOnly(true); + col.setIsUnselectable(true); + col.setUserEditable(false); + col.setFk(new LookupForeignKey(){ + @Override + public TableInfo getLookupTableInfo() + { + // TODO: mock? + String name = queryName + "_sivChallenge"; + UserSchema targetSchema = targetTable.getUserSchema().getDefaultSchema().getUserSchema(targetSchemaName); + QueryDefinition qd = QueryService.get().createQueryDef(u, targetSchemaContainer, targetSchema, name); + qd.setSql("SELECT\n" + + "max(ad.date) as infectionDate,\n" + + // NOTE: CAST() is used to ensure whole numbers + "CONVERT(TIMESTAMPDIFF('SQL_TSI_DAY', CAST(max(ad.date) AS DATE), CAST(c." + dateColName + " AS DATE)), INTEGER) as daysPostInfection,\n" + + "CONVERT(age_in_months(CAST(max(ad.date) AS DATE), CAST(c." + dateColName + " AS DATE)), FLOAT) as monthsPostInfection,\n" + + "c." + pkCol.getFieldKey().toString() + "\n" + + "FROM \"" + schemaName + "\".\"" + queryName + "\" c " + + "JOIN studies.subjectAnchorDates ad ON (ad.subjectId = c." + idCol.getFieldKey().toSQLString() + ")\n" + + "WHERE ad.eventLabel = 'SIV Infection'\n" + + "GROUP BY c.date, c." + pkCol.getFieldKey().toString() + "\n" + + "HAVING count(*) = 1" + ); + qd.setIsTemporary(true); + + List errors = new ArrayList<>(); + TableInfo ti = qd.getTable(errors, true); + if (!errors.isEmpty()) + { + _log.warn("Error creating sivChallenge lookup table for: " + schemaName + "." + queryName + " in container: " + targetSchema.getContainer().getPath()); + for (QueryException e : errors) + { + _log.warn(e.getMessage(), e); + } + } + + if (ti != null) + { + ((BaseColumnInfo)ti.getColumn(pkCol.getName())).setHidden(true); + ((BaseColumnInfo)ti.getColumn(pkCol.getName())).setKeyField(true); + + ((BaseColumnInfo)ti.getColumn("infectionDate")).setLabel("Infection Date"); + ((BaseColumnInfo)ti.getColumn("daysPostInfection")).setLabel("Days Post-Infection"); + ((BaseColumnInfo)ti.getColumn("monthsPostInfection")).setLabel("Months Post-Infection"); + } + + return ti; + } + }); + + targetTable.addColumn(col); + } + + private void appendArtColumns(DatasetTable ds, String subjectColName, String dateColName) + { + String name = "artInformation"; + if (ds.getColumn(name) != null) + return; + + final ColumnInfo pkCol = getPkCol(ds); + if (pkCol == null) + return; + + final ColumnInfo idCol = ds.getColumn(subjectColName); + if (idCol == null) + return; + + final ColumnInfo dateCol = ds.getColumn(dateColName); + if (dateCol == null) + return; + + Dataset treatments = ds.getDataset().getStudy().getDatasetByName("treatments"); + if (treatments == null) + { + return; + } + + final String targetSchemaName = ds.getUserSchema().getName(); + final Container targetSchemaContainer = ds.getUserSchema().getContainer(); + final User u = ds.getUserSchema().getUser(); + final String schemaName = ds.getPublicSchemaName(); + final String queryName = ds.getName(); + + WrappedColumn col = new WrappedColumn(pkCol, name); + col.setLabel("ART Information"); + col.setReadOnly(true); + col.setIsUnselectable(true); + col.setUserEditable(false); + col.setFk(new LookupForeignKey(){ + @Override + public TableInfo getLookupTableInfo() + { + String name = queryName + "_artData"; + UserSchema targetSchema = ds.getUserSchema().getDefaultSchema().getUserSchema(targetSchemaName); + QueryDefinition qd = QueryService.get().createQueryDef(u, targetSchemaContainer, targetSchema, name); + qd.setSql("SELECT\n" + + "max(tr.date) as artInitiation,\n" + + "CONVERT(TIMESTAMPDIFF('SQL_TSI_DAY', CAST(max(tr.date) AS DATE), CAST(c." + dateColName + " AS DATE)), INTEGER) as daysPostArtInitiation,\n" + + "CONVERT(age_in_months(CAST(max(tr.date) AS DATE), CAST(c." + dateColName + " AS DATE)), FLOAT) as monthsPostArtInitiation,\n" + + "max(tr.enddate) as artRelease,\n" + + "CONVERT(CASE WHEN max(tr.enddate) IS NULL THEN NULL ELSE TIMESTAMPDIFF('SQL_TSI_DAY', CAST(max(tr.enddate) AS DATE), CAST(c." + dateColName + " AS DATE)) END, INTEGER) as daysPostArtRelease,\n" + + "CONVERT(CASE WHEN max(tr.enddate) IS NULL THEN NULL ELSE age_in_months(CAST(max(tr.enddate) AS DATE), CAST(c." + dateColName + " AS DATE)) END, FLOAT) as monthsPostArtRelease,\n" + + "CAST(CASE WHEN CAST(max(tr.date) AS DATE) < CAST(c." + dateCol.getFieldKey().toString() + " AS DATE) AND CAST(max(coalesce(tr.enddate, now())) AS DATE) >= CAST(c." + dateCol.getFieldKey().toString() + " AS DATE) THEN 'Y' ELSE null END as VARCHAR) as onArt,\n" + + "GROUP_CONCAT(DISTINCT tr.treatment) AS artTreatment,\n" + + "c." + pkCol.getFieldKey().toString() + "\n" + + "FROM \"" + schemaName + "\".\"" + queryName + "\" c " + + "JOIN study.treatments tr ON (tr.category = 'ART' AND CAST(tr.date AS DATE) <= CAST(c." + dateCol.getFieldKey().toString() + " AS DATE) AND tr.Id = c." + idCol.getFieldKey().toSQLString() + ")\n" + + "GROUP BY c." + dateCol.getFieldKey().toString() + ", c." + pkCol.getFieldKey().toString() + "\n" + + "HAVING COUNT(*) = 1" + ); + qd.setIsTemporary(true); + + List errors = new ArrayList<>(); + TableInfo ti = qd.getTable(errors, true); + if (!errors.isEmpty()) + { + _log.warn("Error creating artData lookup table for: " + schemaName + "." + queryName + " in container: " + targetSchema.getContainer().getPath()); + for (QueryException e : errors) + { + _log.warn(e.getMessage(), e); + } + } + + if (ti != null) + { + ((BaseColumnInfo)ti.getColumn(pkCol.getName())).setHidden(true); + ((BaseColumnInfo)ti.getColumn(pkCol.getName())).setKeyField(true); + + ((BaseColumnInfo)ti.getColumn("artInitiation")).setLabel("ART Initiation"); + ((BaseColumnInfo)ti.getColumn("artRelease")).setLabel("ART Release"); + + ((BaseColumnInfo)ti.getColumn("daysPostArtInitiation")).setLabel("Days Post-ART Initiation"); + ((BaseColumnInfo)ti.getColumn("monthsPostArtInitiation")).setLabel("Months Post-ART Initiation"); + + ((BaseColumnInfo)ti.getColumn("daysPostArtRelease")).setLabel("Days Post-ART Release"); + ((BaseColumnInfo)ti.getColumn("monthsPostArtRelease")).setLabel("Months Post-ART Release"); + + ((BaseColumnInfo)ti.getColumn("artTreatment")).setLabel("ART Treatment(s)"); + ((BaseColumnInfo)ti.getColumn("onArt")).setLabel("Overlaps ART?"); + } + + return ti; + } + }); + + if (ds instanceof AbstractTableInfo ati) + { + ati.addColumn(col); + } + } + + + // TODO: was on ART or not?? + private BaseColumnInfo getWrappedIdCol(UserSchema targetQueryUserSchema, String targetQueryName, AbstractTableInfo demographicsTable, String colName) { WrappedColumn col = new WrappedColumn(demographicsTable.getColumn(ID_COL), colName); @@ -219,4 +479,9 @@ private BaseColumnInfo getWrappedIdCol(UserSchema targetQueryUserSchema, String return col; } + + private void customizeViralLoads(AbstractTableInfo ati) + { + ati.addTriggerFactory(new ViralLoadsTriggerFactory()); + } } diff --git a/SivStudies/src/org/labkey/sivstudies/query/ViralLoadsTriggerFactory.java b/SivStudies/src/org/labkey/sivstudies/query/ViralLoadsTriggerFactory.java new file mode 100644 index 000000000..faab87fe3 --- /dev/null +++ b/SivStudies/src/org/labkey/sivstudies/query/ViralLoadsTriggerFactory.java @@ -0,0 +1,114 @@ +package org.labkey.sivstudies.query; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.labkey.api.data.Container; +import org.labkey.api.data.TableInfo; +import org.labkey.api.data.triggers.Trigger; +import org.labkey.api.data.triggers.TriggerFactory; +import org.labkey.api.query.SimpleValidationError; +import org.labkey.api.query.ValidationException; +import org.labkey.api.security.User; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class ViralLoadsTriggerFactory implements TriggerFactory +{ + public ViralLoadsTriggerFactory() + { + + } + + @Override + public @NotNull Collection createTrigger(@Nullable Container c, TableInfo table, Map extraContext) + { + return List.of(new ViralLoadTrigger()); + } + + public static class ViralLoadTrigger implements Trigger + { + @Override + public void beforeInsert(TableInfo table, Container c, User user, @Nullable Map newRow, ValidationException errors, Map extraContext) throws ValidationException + { + beforeInsert(table, c, user, newRow, errors, extraContext, null); + } + + @Override + public void beforeInsert(TableInfo table, Container c, User user, @Nullable Map newRow, ValidationException errors, Map extraContext, @Nullable Map existingRecord) throws ValidationException + { + handlePVL(newRow, c, errors); + } + + @Override + public void beforeUpdate(TableInfo table, Container c, User user, @Nullable Map newRow, @Nullable Map oldRow, ValidationException errors, Map extraContext) throws ValidationException + { + handlePVL(newRow, c, errors); + } + + /** + * This allows incoming data to specify the study using the string name, which is resolved into the rowId + */ + private void handlePVL(@Nullable Map row, Container c, ValidationException errors) + { + if (row == null) + { + return; + } + + if ("Plasma".equalsIgnoreCase(String.valueOf(row.get("sampleType"))) & row.get("units") == null) + { + row.put("units", "Copies/mL"); + } + + // Enforce consistent case: + if ("Copies/mL".equalsIgnoreCase(String.valueOf(row.get("units")))) + { + row.put("units", "Copies/mL"); + } + + inspectResultValue(row, errors); + + } + + private void inspectResultValue(@NotNull Map row, ValidationException errors) + { + if (row.get("result") == null || NumberUtils.isCreatable(row.get("result").toString())) + { + return; + } + + String val = row.get("result").toString(); + val = val.replaceAll(",", ""); + if (NumberUtils.isCreatable(val)) + { + row.put("result", val); + return; + } + + val = val.toLowerCase(); + if (val.contains("below")) + { + val = StringUtils.trimToNull(val); + val = StringUtils.replaceIgnoreCase(val, "below lod of", ""); + val = StringUtils.replaceIgnoreCase(val, "below", ""); + val = StringUtils.trimToNull(val); + if (NumberUtils.isCreatable(val)) + { + row.put("result", val); + row.put("resultOORIndicator", "<"); + row.put("lod", val); + } + } + + if (!NumberUtils.isCreatable(val)) + { + errors.addError(new SimpleValidationError("Non-numeric VL: [" + row.get("result") + "]", "result", ValidationException.SEVERITY.ERROR)); + } + } + } +} diff --git a/SivStudies/src/org/labkey/sivstudies/study/ArtInitiationEventProvider.java b/SivStudies/src/org/labkey/sivstudies/study/ArtInitiationEventProvider.java new file mode 100644 index 000000000..c627d406d --- /dev/null +++ b/SivStudies/src/org/labkey/sivstudies/study/ArtInitiationEventProvider.java @@ -0,0 +1,58 @@ +package org.labkey.sivstudies.study; + +import org.labkey.api.data.CompareType; +import org.labkey.api.data.Container; +import org.labkey.api.data.SimpleFilter; +import org.labkey.api.data.TableInfo; +import org.labkey.api.data.TableSelector; +import org.labkey.api.module.ModuleLoader; +import org.labkey.api.query.FieldKey; +import org.labkey.api.security.User; +import org.labkey.api.studies.study.AbstractEventProvider; +import org.labkey.api.study.DatasetTable; +import org.labkey.api.util.PageFlowUtil; +import org.labkey.sivstudies.SivStudiesModule; + +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +public class ArtInitiationEventProvider extends AbstractEventProvider +{ + public ArtInitiationEventProvider() + { + super("ART_Initiation", "ART Initiation", "This is the first date when the subject was assigned to the study, as defined in the study assignment table", ModuleLoader.getInstance().getModule(SivStudiesModule.class)); + } + + @Override + protected Map inferDatesRaw(Collection subjectList, Container c, User u) + { + TableInfo ti = getTable(c, u, "study", "treatments"); + if (ti == null) + { + return null; + } + + if (ti instanceof DatasetTable ds) + { + Map ret = new HashMap<>(); + final String subjectCol = ds.getDataset().getStudy().getSubjectColumnName(); + new TableSelector(ti, PageFlowUtil.set(subjectCol, "date"), new SimpleFilter(FieldKey.fromString(subjectCol), subjectList, CompareType.IN), null).forEachResults(rs -> { + String subjectId = rs.getString(FieldKey.fromString(subjectCol)); + Date date = rs.getDate(FieldKey.fromString("date")); + + if (!ret.containsKey(subjectId) || date.before(ret.get(subjectId))) + { + ret.put(subjectId, date); + } + }); + + return ret; + } + else + { + throw new IllegalStateException("Expected study.assignment to be a DatasetTable"); + } + } +} diff --git a/SivStudies/src/org/labkey/sivstudies/study/SivInfectionEventProvider.java b/SivStudies/src/org/labkey/sivstudies/study/SivInfectionEventProvider.java new file mode 100644 index 000000000..6ff2c177f --- /dev/null +++ b/SivStudies/src/org/labkey/sivstudies/study/SivInfectionEventProvider.java @@ -0,0 +1,62 @@ +package org.labkey.sivstudies.study; + +import org.labkey.api.data.CompareType; +import org.labkey.api.data.Container; +import org.labkey.api.data.SimpleFilter; +import org.labkey.api.data.TableInfo; +import org.labkey.api.data.TableSelector; +import org.labkey.api.module.ModuleLoader; +import org.labkey.api.query.FieldKey; +import org.labkey.api.query.QueryService; +import org.labkey.api.query.UserSchema; +import org.labkey.api.security.User; +import org.labkey.api.security.permissions.ReadPermission; +import org.labkey.api.studies.study.AbstractEventProvider; +import org.labkey.api.study.DatasetTable; +import org.labkey.api.util.PageFlowUtil; +import org.labkey.sivstudies.SivStudiesModule; + +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +public class SivInfectionEventProvider extends AbstractEventProvider +{ + public SivInfectionEventProvider() + { + super("SIV_Infection", "SIV Infection", "This is the official date of SIV infection, used to calculate days-post-infection", ModuleLoader.getInstance().getModule(SivStudiesModule.class)); + } + + @Override + protected Map inferDatesRaw(Collection subjectList, Container c, User u) + { + UserSchema us = QueryService.get().getUserSchema(u, c, "study"); + if (us == null) + { + return Collections.emptyMap(); + } + + TableInfo ti = us.getTable("assignment"); + if (ti == null || !ti.hasPermission(u, ReadPermission.class)) + { + return Collections.emptyMap(); + } + + if (ti instanceof DatasetTable ds) + { + Map ret = new HashMap<>(); + final String subjectCol = ds.getDataset().getStudy().getSubjectColumnName(); + new TableSelector(ti, PageFlowUtil.set(subjectCol, "date"), new SimpleFilter(FieldKey.fromString(subjectCol), subjectList, CompareType.IN), null).forEachResults(rs -> { + ret.put(rs.getString(FieldKey.fromString(subjectCol)), rs.getDate(FieldKey.fromString("date"))); + }); + + return ret; + } + else + { + throw new IllegalStateException("Expected study.assignment to be a DatasetTable"); + } + } +} diff --git a/mGAP/src/org/labkey/mgap/etl/UpdatePedigreeStep.java b/mGAP/src/org/labkey/mgap/etl/UpdatePedigreeStep.java index 84e93eb17..dd50d7ae8 100644 --- a/mGAP/src/org/labkey/mgap/etl/UpdatePedigreeStep.java +++ b/mGAP/src/org/labkey/mgap/etl/UpdatePedigreeStep.java @@ -16,6 +16,7 @@ import org.labkey.api.query.InvalidKeyException; import org.labkey.api.query.QueryException; import org.labkey.api.query.QueryService; +import org.labkey.api.query.QueryUpdateService; import org.labkey.api.query.QueryUpdateServiceException; import org.labkey.api.query.UserSchema; import org.labkey.api.util.PageFlowUtil; @@ -129,7 +130,9 @@ public RecordedActionSet run(@NotNull PipelineJob job) throws PipelineJobExcepti try { - subjectsTable.getUpdateService().updateRows(job.getUser(), job.getContainer(), toUpdate, oldKeys, null, new HashMap<>()); + QueryUpdateService qus = subjectsTable.getUpdateService(); + qus.setBulkLoad(true); + qus.updateRows(job.getUser(), job.getContainer(), toUpdate, oldKeys, null, new HashMap<>()); } catch (QueryException | BatchValidationException | SQLException | InvalidKeyException | QueryUpdateServiceException e) { diff --git a/mcc/src/org/labkey/mcc/etl/PopulateGeneticDataStep.java b/mcc/src/org/labkey/mcc/etl/PopulateGeneticDataStep.java index a71c552d0..9e27a7c4e 100644 --- a/mcc/src/org/labkey/mcc/etl/PopulateGeneticDataStep.java +++ b/mcc/src/org/labkey/mcc/etl/PopulateGeneticDataStep.java @@ -17,6 +17,7 @@ import org.labkey.api.query.DuplicateKeyException; import org.labkey.api.query.FieldKey; import org.labkey.api.query.QueryService; +import org.labkey.api.query.QueryUpdateService; import org.labkey.api.query.QueryUpdateServiceException; import org.labkey.api.util.PageFlowUtil; import org.labkey.api.writer.ContainerUser; @@ -118,12 +119,15 @@ private void populateGeneticData(PipelineJob job) throws PipelineJobException TableInfo gd = QueryService.get().getUserSchema(job.getUser(), child, "study").getTable("genomicDatasets"); try { - gd.getUpdateService().truncateRows(job.getUser(), child, null, null); + QueryUpdateService qus = gd.getUpdateService(); + qus.setBulkLoad(true); + + qus.truncateRows(job.getUser(), child, null, null); if (toInsert.containsKey(child)) { BatchValidationException bve = new BatchValidationException(); - gd.getUpdateService().insertRows(job.getUser(), child, toInsert.get(child), bve, null, null); + qus.insertRows(job.getUser(), child, toInsert.get(child), bve, null, null); if (bve.hasErrors()) { throw bve; diff --git a/mcc/src/org/labkey/mcc/etl/PopulateIdsStep.java b/mcc/src/org/labkey/mcc/etl/PopulateIdsStep.java index 81debad62..5f1f6d4bc 100644 --- a/mcc/src/org/labkey/mcc/etl/PopulateIdsStep.java +++ b/mcc/src/org/labkey/mcc/etl/PopulateIdsStep.java @@ -18,6 +18,7 @@ import org.labkey.api.query.DuplicateKeyException; import org.labkey.api.query.FieldKey; import org.labkey.api.query.QueryService; +import org.labkey.api.query.QueryUpdateService; import org.labkey.api.query.QueryUpdateServiceException; import org.labkey.api.query.UserSchema; import org.labkey.api.util.PageFlowUtil; @@ -146,7 +147,9 @@ private void populateForField(PipelineJob job, TableInfo sourceTi, String fieldN job.getLogger().info("Total IDs to alias for field " + fieldName + " in container: " + c.getPath() + ": " + toAdd.get(c).size()); TableInfo ti = QueryService.get().getUserSchema(_containerUser.getUser(), c, MccSchema.NAME).getTable(MccSchema.TABLE_ANIMAL_MAPPING); BatchValidationException bve = new BatchValidationException(); - ti.getUpdateService().insertRows(_containerUser.getUser(), c, toAdd.get(c), bve, null, null); + QueryUpdateService qus = ti.getUpdateService(); + qus.setBulkLoad(true); + qus.insertRows(_containerUser.getUser(), c, toAdd.get(c), bve, null, null); if (bve.hasErrors()) { throw bve; diff --git a/primeseq/build.gradle b/primeseq/build.gradle index 05f2ed677..a48562508 100644 --- a/primeseq/build.gradle +++ b/primeseq/build.gradle @@ -12,6 +12,7 @@ dependencies { BuildUtils.addLabKeyDependency(project: project, config: "implementation", depProjectPath: ":server:modules:DiscvrLabKeyModules:SequenceAnalysis", depProjectConfig: "apiJarFile") BuildUtils.addLabKeyDependency(project: project, config: "implementation", depProjectPath: ":server:modules:DiscvrLabKeyModules:cluster", depProjectConfig: "apiJarFile") BuildUtils.addLabKeyDependency(project: project, config: "implementation", depProjectPath: ":server:modules:DiscvrLabKeyModules:jbrowse", depProjectConfig: "apiJarFile") + BuildUtils.addLabKeyDependency(project: project, config: "implementation", depProjectPath: ":server:modules:DiscvrLabKeyModules:discvrcore", depProjectConfig: "apiJarFile") BuildUtils.addLabKeyDependency(project: project, config: "modules", depProjectPath: ":server:modules:LabDevKitModules:laboratory", depProjectConfig: "published", depExtension: "module") BuildUtils.addLabKeyDependency(project: project, config: "modules", depProjectPath: ":server:modules:LabDevKitModules:LDK", depProjectConfig: "published", depExtension: "module") diff --git a/primeseq/src/org/labkey/primeseq/PrimeseqController.java b/primeseq/src/org/labkey/primeseq/PrimeseqController.java index 3606da7ef..bcd589964 100644 --- a/primeseq/src/org/labkey/primeseq/PrimeseqController.java +++ b/primeseq/src/org/labkey/primeseq/PrimeseqController.java @@ -33,12 +33,8 @@ import org.labkey.api.data.ContainerType; import org.labkey.api.data.DbScope; import org.labkey.api.data.SQLFragment; -import org.labkey.api.data.SimpleFilter; import org.labkey.api.data.SqlExecutor; -import org.labkey.api.data.TableSelector; -import org.labkey.api.exp.api.ExpData; -import org.labkey.api.exp.api.ExpRun; -import org.labkey.api.exp.api.ExperimentService; +import org.labkey.api.discvrcore.annotation.UtilityAction; import org.labkey.api.module.Module; import org.labkey.api.module.ModuleLoader; import org.labkey.api.pipeline.PipeRoot; @@ -47,13 +43,10 @@ import org.labkey.api.pipeline.PipelineService; import org.labkey.api.pipeline.PipelineStatusFile; import org.labkey.api.pipeline.PipelineUrls; -import org.labkey.api.query.FieldKey; -import org.labkey.api.query.QueryService; import org.labkey.api.security.RequiresPermission; import org.labkey.api.security.RequiresSiteAdmin; import org.labkey.api.security.permissions.ReadPermission; import org.labkey.api.security.permissions.UpdatePermission; -import org.labkey.api.sequenceanalysis.SequenceOutputFile; import org.labkey.api.sequenceanalysis.pipeline.HasJobParams; import org.labkey.api.sequenceanalysis.pipeline.JobResourceSettings; import org.labkey.api.sequenceanalysis.pipeline.SequencePipelineService; @@ -229,6 +222,7 @@ public URLHelper getSuccessURL(Object o) } } + @UtilityAction(label = "Update File Paths", description = "This action is designed to bulk update filepaths stored in the database, such as when a folder's file root is updated. This circumvents LabKey's normal file update listeners, and should only be performed if you are certain this is what you want. A reason for this is because the default codepath can be slow with extremely large moves.") @RequiresSiteAdmin public static class UpdateFilePathsAction extends ConfirmAction { @@ -631,6 +625,7 @@ public Collection getJobRowIds() } } + @UtilityAction(label = "Perform MHC Cleanup", description = "This will run a pipeline job to delete low-frequency MHC results to save space") @RequiresPermission(UpdatePermission.class) public static class PerformMhcCleanupAction extends ConfirmAction { @@ -803,77 +798,4 @@ public void setRestartJobs(boolean restartJobs) _restartJobs = restartJobs; } } - - @RequiresSiteAdmin - public static class FixSbtAction extends ConfirmAction - { - @Override - public ModelAndView getConfirmView(Object o, BindException errors) throws Exception - { - setTitle("Fix SBT Errors"); - - return new HtmlView(HtmlString.of("This will update filepaths on SBT outputs. Do you want to continue?")); - } - - @Override - public boolean handlePost(Object o, BindException errors) throws Exception - { - new TableSelector(QueryService.get().getUserSchema(getUser(), getContainer(), "sequenceanalysis").getTable("outputfiles"), PageFlowUtil.set("rowid"), new SimpleFilter(FieldKey.fromString("category"), "SBT Results"), null).forEachResults(rs -> { - SequenceOutputFile so = SequenceOutputFile.getForId(rs.getInt(FieldKey.fromString("rowid"))); - - File f = so.getFile(); - if (f.exists()) - { - return; - } - - ExpRun run = ExperimentService.get().getExpRun(so.getRunId()); - PipelineStatusFile sf = PipelineService.get().getStatusFile(run.getJobId()); - File logFile = new File(sf.getFilePath()); - File root = logFile.getParentFile(); - File [] dirs = root.listFiles(fn -> { - return fn.isDirectory() & !fn.getName().equalsIgnoreCase("Shared"); - }); - - if (dirs == null || dirs.length == 0) - { - _log.error("Unable to file directory for: " + f.getPath()); - return; - } - - File parent = new File(dirs[0], "Alignment"); - File [] children = parent.listFiles(fn -> { - return fn.getName().endsWith(".sbt_hits.txt.gz"); - }); - - if (children == null || children.length != 1) - { - _log.error("Unable to file child under: " + parent.getPath()); - return; - } - - _log.info("Found: " + children[0].getPath()); - - ExpData d = so.getExpData(); - d.setDataFileURI(children[0].toURI()); - - d.save(getUser()); - }); - - return true; - } - - @Override - public void validateCommand(Object o, Errors errors) - { - - } - - @NotNull - @Override - public URLHelper getSuccessURL(Object o) - { - return PageFlowUtil.urlProvider(PipelineUrls.class).urlBegin(getContainer()); - } - } } \ No newline at end of file diff --git a/primeseq/src/org/labkey/primeseq/notification/DiskUsageNotification.java b/primeseq/src/org/labkey/primeseq/notification/DiskUsageNotification.java index 7245af41c..799965cf3 100644 --- a/primeseq/src/org/labkey/primeseq/notification/DiskUsageNotification.java +++ b/primeseq/src/org/labkey/primeseq/notification/DiskUsageNotification.java @@ -5,8 +5,8 @@ import org.apache.commons.lang3.time.DurationFormatUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; import org.labkey.api.data.Container; -import org.labkey.api.data.PropertyManager; import org.labkey.api.ldk.notification.Notification; import org.labkey.api.pipeline.PipelineJobException; import org.labkey.api.security.User; @@ -14,12 +14,17 @@ import org.labkey.api.settings.LookAndFeelProperties; import java.text.DateFormat; -import java.text.NumberFormat; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; import java.util.Date; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -29,10 +34,6 @@ public class DiskUsageNotification implements Notification { protected final static Logger _log = LogManager.getLogger(DiskUsageNotification.class); - private static final String lastSave = "lastSave"; - private NumberFormat _pctFormat = null; - - private static final String PROP_CATEGORY = "primeseq.DiskUsageNotification"; @Override public String getName() @@ -81,19 +82,11 @@ public String getScheduleDescription() return "Every Monday at 8AM"; } - private Map getSavedValues(Container c) - { - return PropertyManager.getProperties(c, PROP_CATEGORY); - } - @Override public String getMessageBodyHTML(Container c, User u) { Date start = new Date(); - _pctFormat = NumberFormat.getPercentInstance(); - _pctFormat.setMaximumFractionDigits(1); - StringBuilder msg = new StringBuilder(); getDiskUsageStats(c, u, msg); getClusterUsage(c, u, msg); @@ -110,12 +103,29 @@ private void getClusterUsage(Container c, User u, final StringBuilder msg) return; } + List> byMonth = getClusterUsageByMonth(Arrays.asList("bimberlab", "onprcgenetics"), 12); + byMonth.sort(Comparator.comparing(o -> String.valueOf(o.get("Account")))); + + msg.append("Cluster Usage By Month:"); + msg.append("AccountMonthCPUGPUCompute Units"); + byMonth.forEach(map -> { + long cpu = (Long)map.get("CPU"); + long gpu = (Long)map.get("GPU"); + double units = ((double)cpu/6000) + ((double)gpu/600); + Date start = (Date)map.get("Start"); + + msg.append("").append(map.get("Account")).append("").append(getDateTimeFormat(c).format(start)).append("").append(String.format("%,d", cpu)).append("").append(String.format("%,d", gpu)).append("").append(String.format("%,.2f", units)).append(""); + }); + + msg.append(""); + msg.append("\n"); + try { SimpleScriptWrapper wrapper = new SimpleScriptWrapper(_log); String results = wrapper.executeWithOutput(Arrays.asList("ssh", "-q", "labkey_submit@arc", "sshare", "-U", "-u", "labkey_submit")); - msg.append("Cluster Usage:"); + msg.append("Cluster Priority By Account:"); msg.append("AccountNormSharesRawUsageEffectiveUsageFairShare"); AtomicBoolean foundHeader = new AtomicBoolean(false); @@ -148,14 +158,12 @@ else if (!foundHeader.get()) msg.append(""); }); msg.append(""); + msg.append("\n"); } catch (PipelineJobException e) { _log.error("Error fetching slurm summary", e); } - - msg.append("\n"); - } private void getDiskUsageStats(Container c, User u, final StringBuilder msg) @@ -194,6 +202,107 @@ private void getDiskUsageStats(Container c, User u, final StringBuilder msg) _log.error("Error running df", e); } - msg.append("\n"); + msg.append("\n"); + } + + private List> getClusterUsageByMonth(List accounts, int numMonths) + { + Calendar currentCal = Calendar.getInstance(); + int currentMonth = currentCal.get(Calendar.MONTH); + int currentYear = currentCal.get(Calendar.YEAR); + + List> results = new ArrayList<>(); + + int offset = 0; + while (offset < numMonths) + { + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.YEAR, currentYear); + cal.set(Calendar.MONTH, currentMonth - offset); + cal.set(Calendar.DAY_OF_MONTH, 1); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + Date start = cal.getTime(); + + Calendar endCal = Calendar.getInstance(); + endCal.setTime(start); + endCal.set(Calendar.DAY_OF_MONTH, endCal.getActualMaximum(Calendar.DAY_OF_MONTH)); + + // Set to last second of the day: + endCal.add(Calendar.DATE, 1); + endCal.add(Calendar.MILLISECOND, -1); + Date end = endCal.getTime(); + + results.addAll(getClusterUsageForInterval(accounts, start, end)); + + offset++; + } + + return results; + } + + private @NotNull List> getClusterUsageForInterval(List accounts, Date start, Date end) + { + if (!SystemUtils.IS_OS_LINUX) + { + return Collections.emptyList(); + } + + final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + + try + { + List args = new ArrayList<>(Arrays.asList("ssh", "-q", "labkey_submit@arc", "/usr/local/bin/sreport-accts-summary", "Accounts=" + StringUtils.join(accounts, ","))); + if (start != null) + { + args.add("Start=" + dateFormat.format(start)); + } + + if (end != null) + { + args.add("End=" + dateFormat.format(end)); + } + + SimpleScriptWrapper wrapper = new SimpleScriptWrapper(_log); + String results = wrapper.executeWithOutput(args); + + AtomicBoolean foundHeader = new AtomicBoolean(false); + List> ret = Arrays.stream(results.split("\n")).map(x -> { + if (x.startsWith("Account|")) + { + foundHeader.set(true); + return null; + } + else if (!foundHeader.get()) + { + return null; + } + + String[] els = x.split("\\|"); + + if (els.length != 3) + { + _log.error("Unexpected line: " + StringUtils.join(els, "<>")); + return null; + } + + Map map = new HashMap<>(Map.of("Account", els[0], "CPU", Long.parseLong(els[1]), "GPU", Long.parseLong(els[2]))); + return map; + }).filter(Objects::nonNull).toList(); + + ret.forEach(map -> { + map.put("Start", start); + map.put("End", end); + }); + + return ret; + } + catch (PipelineJobException e) + { + _log.error("Error fetching slurm summary", e); + return Collections.emptyList(); + } } } \ No newline at end of file diff --git a/primeseq/src/org/labkey/primeseq/pipeline/BlastPipelineJobResourceAllocator.java b/primeseq/src/org/labkey/primeseq/pipeline/BlastPipelineJobResourceAllocator.java index 73e1ab109..7f2a687e6 100644 --- a/primeseq/src/org/labkey/primeseq/pipeline/BlastPipelineJobResourceAllocator.java +++ b/primeseq/src/org/labkey/primeseq/pipeline/BlastPipelineJobResourceAllocator.java @@ -40,7 +40,7 @@ public Integer getMaxRequestMemory(PipelineJob job) } @Override - public void addExtraSubmitScriptLines(PipelineJob job, RemoteExecutionEngine engine, List lines) + public void addExtraSubmitScriptLines(PipelineJob job, RemoteExecutionEngine> engine, List lines) { //force BLAST jobs to top of queue, since we assume these run quickly if ("HTCondorEngine".equals(engine.getType())) diff --git a/primeseq/src/org/labkey/primeseq/pipeline/SequenceJobResourceAllocator.java b/primeseq/src/org/labkey/primeseq/pipeline/SequenceJobResourceAllocator.java index 1151b766f..c213266fd 100644 --- a/primeseq/src/org/labkey/primeseq/pipeline/SequenceJobResourceAllocator.java +++ b/primeseq/src/org/labkey/primeseq/pipeline/SequenceJobResourceAllocator.java @@ -116,8 +116,8 @@ public Integer getMaxRequestCpus(PipelineJob job) if (isSequenceNormalizationTask(job)) { - job.getLogger().debug("setting max CPUs to 4"); - return 4; + job.getLogger().debug("setting max CPUs to 2"); + return 2; } if (isLuceneIndexJob(job)) @@ -137,15 +137,15 @@ public Integer getMaxRequestCpus(PipelineJob job) //10gb if (totalFileSize < 10e9) { - job.getLogger().debug("file size less than 10gb, lowering CPUs to 8"); + job.getLogger().debug("file size less than 10gb, lowering CPUs to 4"); - return 8; + return 4; } else if (totalFileSize < 20e9) { - job.getLogger().debug("file size less than 20gb, lowering CPUs to 12"); + job.getLogger().debug("file size less than 20gb, lowering CPUs to 8"); - return 12; + return 8; } job.getLogger().debug("file size greater than 20gb, using 12 CPUs"); @@ -318,7 +318,7 @@ public Integer getMaxRequestMemory(PipelineJob job) } @Override - public void addExtraSubmitScriptLines(PipelineJob job, RemoteExecutionEngine engine, List lines) + public void addExtraSubmitScriptLines(PipelineJob job, RemoteExecutionEngine> engine, List lines) { if (job instanceof HasJobParams) { @@ -332,7 +332,7 @@ public void addExtraSubmitScriptLines(PipelineJob job, RemoteExecutionEngine eng } @Override - public @NotNull Map getEnvironmentVars(PipelineJob job, RemoteExecutionEngine engine) + public @NotNull Map getEnvironmentVars(PipelineJob job, RemoteExecutionEngine> engine) { Map ret = new HashMap<>(); @@ -358,7 +358,7 @@ private String getTime(PipelineJob job) return null; } - private void possiblyAddHighIO(PipelineJob job, RemoteExecutionEngine engine, List lines) + private void possiblyAddHighIO(PipelineJob job, RemoteExecutionEngine> engine, List lines) { Map params = ((HasJobParams)job).getJobParams(); String val = StringUtils.trimToNull(params.get("resourceSettings.resourceSettings.highIO")); @@ -522,7 +522,7 @@ private void possiblyAddQOS(PipelineJob job, RemoteExecutionEngine engine, List< job.getLogger().debug("qos as supplied by job: " + qos); //Exempt specific task types: - TaskFactory factory = job.getActiveTaskFactory(); + TaskFactory> factory = job.getActiveTaskFactory(); String activeTask = factory == null ? null : factory.getId().getNamespaceClass().getSimpleName(); job.getLogger().debug("Active task simplename: " + activeTask); if (Arrays.asList("PrepareAlignerIndexesTask", "CacheAlignerIndexesTask", "AlignmentInitTask", "VariantProcessingScatterRemotePrepareTask").contains(activeTask)) @@ -610,7 +610,7 @@ private Long getFileSize(PipelineJob job) } @Override - public void processJavaOpts(PipelineJob job, RemoteExecutionEngine engine, @NotNull List existingJavaOpts) + public void processJavaOpts(PipelineJob job, RemoteExecutionEngine> engine, @NotNull List existingJavaOpts) { if (job instanceof HasJobParams) { diff --git a/tcrdb/resources/queries/tcrdb/clone_responses/.qview.xml b/tcrdb/resources/queries/tcrdb/clone_responses/.qview.xml new file mode 100644 index 000000000..d07972bdb --- /dev/null +++ b/tcrdb/resources/queries/tcrdb/clone_responses/.qview.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tcrdb/resources/queries/tcrdb/clone_responses/Cohort Info.qview.xml b/tcrdb/resources/queries/tcrdb/clone_responses/Cohort Info.qview.xml new file mode 100644 index 000000000..4e7d72c87 --- /dev/null +++ b/tcrdb/resources/queries/tcrdb/clone_responses/Cohort Info.qview.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tcrdb/resources/queries/tcrdb/stims/.qview.xml b/tcrdb/resources/queries/tcrdb/stims/.qview.xml index 65170b8d8..e00aaa911 100644 --- a/tcrdb/resources/queries/tcrdb/stims/.qview.xml +++ b/tcrdb/resources/queries/tcrdb/stims/.qview.xml @@ -11,6 +11,7 @@ + diff --git a/tcrdb/resources/queries/tcrdb/stims/Cohort Info.qview.xml b/tcrdb/resources/queries/tcrdb/stims/Cohort Info.qview.xml new file mode 100644 index 000000000..2cf6da768 --- /dev/null +++ b/tcrdb/resources/queries/tcrdb/stims/Cohort Info.qview.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tcrdb/resources/schemas/dbscripts/postgresql/tcrdb-15.54-15.55.sql b/tcrdb/resources/schemas/dbscripts/postgresql/tcrdb-15.54-15.55.sql new file mode 100644 index 000000000..db2c19102 --- /dev/null +++ b/tcrdb/resources/schemas/dbscripts/postgresql/tcrdb-15.54-15.55.sql @@ -0,0 +1,7 @@ +ALTER TABLE tcrdb.clone_responses ADD vGene varchar(1000); +ALTER TABLE tcrdb.clone_responses ADD totalCells int; +ALTER TABLE tcrdb.clone_responses ADD totalCloneSize int; +ALTER TABLE tcrdb.clone_responses ADD fractionCloneActivated double precision; +ALTER TABLE tcrdb.clone_responses ADD totalCellsForSample int; +ALTER TABLE tcrdb.clone_responses ADD oddsRatio double precision; +ALTER TABLE tcrdb.clone_responses ADD enrichmentFDR double precision; diff --git a/tcrdb/resources/schemas/dbscripts/postgresql/tcrdb-15.55-15.56.sql b/tcrdb/resources/schemas/dbscripts/postgresql/tcrdb-15.55-15.56.sql new file mode 100644 index 000000000..b42680c3d --- /dev/null +++ b/tcrdb/resources/schemas/dbscripts/postgresql/tcrdb-15.55-15.56.sql @@ -0,0 +1 @@ +ALTER TABLE tcrdb.clone_responses ADD jGene varchar(1000); diff --git a/tcrdb/resources/schemas/dbscripts/postgresql/tcrdb-15.56-15.57.sql b/tcrdb/resources/schemas/dbscripts/postgresql/tcrdb-15.56-15.57.sql new file mode 100644 index 000000000..21758fc7e --- /dev/null +++ b/tcrdb/resources/schemas/dbscripts/postgresql/tcrdb-15.56-15.57.sql @@ -0,0 +1 @@ +ALTER TABLE tcrdb.stims ADD nClones int; diff --git a/tcrdb/resources/schemas/dbscripts/sqlserver/tcrdb-15.54-15.55.sql b/tcrdb/resources/schemas/dbscripts/sqlserver/tcrdb-15.54-15.55.sql new file mode 100644 index 000000000..db2c19102 --- /dev/null +++ b/tcrdb/resources/schemas/dbscripts/sqlserver/tcrdb-15.54-15.55.sql @@ -0,0 +1,7 @@ +ALTER TABLE tcrdb.clone_responses ADD vGene varchar(1000); +ALTER TABLE tcrdb.clone_responses ADD totalCells int; +ALTER TABLE tcrdb.clone_responses ADD totalCloneSize int; +ALTER TABLE tcrdb.clone_responses ADD fractionCloneActivated double precision; +ALTER TABLE tcrdb.clone_responses ADD totalCellsForSample int; +ALTER TABLE tcrdb.clone_responses ADD oddsRatio double precision; +ALTER TABLE tcrdb.clone_responses ADD enrichmentFDR double precision; diff --git a/tcrdb/resources/schemas/dbscripts/sqlserver/tcrdb-15.55-15.56.sql b/tcrdb/resources/schemas/dbscripts/sqlserver/tcrdb-15.55-15.56.sql new file mode 100644 index 000000000..b42680c3d --- /dev/null +++ b/tcrdb/resources/schemas/dbscripts/sqlserver/tcrdb-15.55-15.56.sql @@ -0,0 +1 @@ +ALTER TABLE tcrdb.clone_responses ADD jGene varchar(1000); diff --git a/tcrdb/resources/schemas/dbscripts/sqlserver/tcrdb-15.56-15.57.sql b/tcrdb/resources/schemas/dbscripts/sqlserver/tcrdb-15.56-15.57.sql new file mode 100644 index 000000000..21758fc7e --- /dev/null +++ b/tcrdb/resources/schemas/dbscripts/sqlserver/tcrdb-15.56-15.57.sql @@ -0,0 +1 @@ +ALTER TABLE tcrdb.stims ADD nClones int; diff --git a/tcrdb/resources/schemas/tcrdb.xml b/tcrdb/resources/schemas/tcrdb.xml index 88951dd56..60a93c0ad 100644 --- a/tcrdb/resources/schemas/tcrdb.xml +++ b/tcrdb/resources/schemas/tcrdb.xml @@ -134,6 +134,30 @@ Background Freq + + V-Gene + + + J-Gene + + + Total Cells + + + Total Clone Size + + + Fraction of Clone Activated + + + Total Cells For Sample + + + Enrichment (Odds Ratio) + + + Enrichment (FDR) + @@ -296,6 +320,9 @@ Flow Quantification + + # Clones + diff --git a/tcrdb/src/org/labkey/tcrdb/TCRdbModule.java b/tcrdb/src/org/labkey/tcrdb/TCRdbModule.java index dd7b9d912..d452b218b 100644 --- a/tcrdb/src/org/labkey/tcrdb/TCRdbModule.java +++ b/tcrdb/src/org/labkey/tcrdb/TCRdbModule.java @@ -46,7 +46,7 @@ public String getName() @Override public Double getSchemaVersion() { - return 15.54; + return 15.57; } @Override
"); + msg.append("
"); + msg.append("Cluster Priority By Account:
"); msg.append("
\n"); - } private void getDiskUsageStats(Container c, User u, final StringBuilder msg) @@ -194,6 +202,107 @@ private void getDiskUsageStats(Container c, User u, final StringBuilder msg) _log.error("Error running df", e); } - msg.append("
\n"); + msg.append("\n"); + } + + private List> getClusterUsageByMonth(List accounts, int numMonths) + { + Calendar currentCal = Calendar.getInstance(); + int currentMonth = currentCal.get(Calendar.MONTH); + int currentYear = currentCal.get(Calendar.YEAR); + + List> results = new ArrayList<>(); + + int offset = 0; + while (offset < numMonths) + { + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.YEAR, currentYear); + cal.set(Calendar.MONTH, currentMonth - offset); + cal.set(Calendar.DAY_OF_MONTH, 1); + cal.set(Calendar.HOUR_OF_DAY, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + Date start = cal.getTime(); + + Calendar endCal = Calendar.getInstance(); + endCal.setTime(start); + endCal.set(Calendar.DAY_OF_MONTH, endCal.getActualMaximum(Calendar.DAY_OF_MONTH)); + + // Set to last second of the day: + endCal.add(Calendar.DATE, 1); + endCal.add(Calendar.MILLISECOND, -1); + Date end = endCal.getTime(); + + results.addAll(getClusterUsageForInterval(accounts, start, end)); + + offset++; + } + + return results; + } + + private @NotNull List> getClusterUsageForInterval(List accounts, Date start, Date end) + { + if (!SystemUtils.IS_OS_LINUX) + { + return Collections.emptyList(); + } + + final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + + try + { + List args = new ArrayList<>(Arrays.asList("ssh", "-q", "labkey_submit@arc", "/usr/local/bin/sreport-accts-summary", "Accounts=" + StringUtils.join(accounts, ","))); + if (start != null) + { + args.add("Start=" + dateFormat.format(start)); + } + + if (end != null) + { + args.add("End=" + dateFormat.format(end)); + } + + SimpleScriptWrapper wrapper = new SimpleScriptWrapper(_log); + String results = wrapper.executeWithOutput(args); + + AtomicBoolean foundHeader = new AtomicBoolean(false); + List> ret = Arrays.stream(results.split("\n")).map(x -> { + if (x.startsWith("Account|")) + { + foundHeader.set(true); + return null; + } + else if (!foundHeader.get()) + { + return null; + } + + String[] els = x.split("\\|"); + + if (els.length != 3) + { + _log.error("Unexpected line: " + StringUtils.join(els, "<>")); + return null; + } + + Map map = new HashMap<>(Map.of("Account", els[0], "CPU", Long.parseLong(els[1]), "GPU", Long.parseLong(els[2]))); + return map; + }).filter(Objects::nonNull).toList(); + + ret.forEach(map -> { + map.put("Start", start); + map.put("End", end); + }); + + return ret; + } + catch (PipelineJobException e) + { + _log.error("Error fetching slurm summary", e); + return Collections.emptyList(); + } } } \ No newline at end of file diff --git a/primeseq/src/org/labkey/primeseq/pipeline/BlastPipelineJobResourceAllocator.java b/primeseq/src/org/labkey/primeseq/pipeline/BlastPipelineJobResourceAllocator.java index 73e1ab109..7f2a687e6 100644 --- a/primeseq/src/org/labkey/primeseq/pipeline/BlastPipelineJobResourceAllocator.java +++ b/primeseq/src/org/labkey/primeseq/pipeline/BlastPipelineJobResourceAllocator.java @@ -40,7 +40,7 @@ public Integer getMaxRequestMemory(PipelineJob job) } @Override - public void addExtraSubmitScriptLines(PipelineJob job, RemoteExecutionEngine engine, List lines) + public void addExtraSubmitScriptLines(PipelineJob job, RemoteExecutionEngine> engine, List lines) { //force BLAST jobs to top of queue, since we assume these run quickly if ("HTCondorEngine".equals(engine.getType())) diff --git a/primeseq/src/org/labkey/primeseq/pipeline/SequenceJobResourceAllocator.java b/primeseq/src/org/labkey/primeseq/pipeline/SequenceJobResourceAllocator.java index 1151b766f..c213266fd 100644 --- a/primeseq/src/org/labkey/primeseq/pipeline/SequenceJobResourceAllocator.java +++ b/primeseq/src/org/labkey/primeseq/pipeline/SequenceJobResourceAllocator.java @@ -116,8 +116,8 @@ public Integer getMaxRequestCpus(PipelineJob job) if (isSequenceNormalizationTask(job)) { - job.getLogger().debug("setting max CPUs to 4"); - return 4; + job.getLogger().debug("setting max CPUs to 2"); + return 2; } if (isLuceneIndexJob(job)) @@ -137,15 +137,15 @@ public Integer getMaxRequestCpus(PipelineJob job) //10gb if (totalFileSize < 10e9) { - job.getLogger().debug("file size less than 10gb, lowering CPUs to 8"); + job.getLogger().debug("file size less than 10gb, lowering CPUs to 4"); - return 8; + return 4; } else if (totalFileSize < 20e9) { - job.getLogger().debug("file size less than 20gb, lowering CPUs to 12"); + job.getLogger().debug("file size less than 20gb, lowering CPUs to 8"); - return 12; + return 8; } job.getLogger().debug("file size greater than 20gb, using 12 CPUs"); @@ -318,7 +318,7 @@ public Integer getMaxRequestMemory(PipelineJob job) } @Override - public void addExtraSubmitScriptLines(PipelineJob job, RemoteExecutionEngine engine, List lines) + public void addExtraSubmitScriptLines(PipelineJob job, RemoteExecutionEngine> engine, List lines) { if (job instanceof HasJobParams) { @@ -332,7 +332,7 @@ public void addExtraSubmitScriptLines(PipelineJob job, RemoteExecutionEngine eng } @Override - public @NotNull Map getEnvironmentVars(PipelineJob job, RemoteExecutionEngine engine) + public @NotNull Map getEnvironmentVars(PipelineJob job, RemoteExecutionEngine> engine) { Map ret = new HashMap<>(); @@ -358,7 +358,7 @@ private String getTime(PipelineJob job) return null; } - private void possiblyAddHighIO(PipelineJob job, RemoteExecutionEngine engine, List lines) + private void possiblyAddHighIO(PipelineJob job, RemoteExecutionEngine> engine, List lines) { Map params = ((HasJobParams)job).getJobParams(); String val = StringUtils.trimToNull(params.get("resourceSettings.resourceSettings.highIO")); @@ -522,7 +522,7 @@ private void possiblyAddQOS(PipelineJob job, RemoteExecutionEngine engine, List< job.getLogger().debug("qos as supplied by job: " + qos); //Exempt specific task types: - TaskFactory factory = job.getActiveTaskFactory(); + TaskFactory> factory = job.getActiveTaskFactory(); String activeTask = factory == null ? null : factory.getId().getNamespaceClass().getSimpleName(); job.getLogger().debug("Active task simplename: " + activeTask); if (Arrays.asList("PrepareAlignerIndexesTask", "CacheAlignerIndexesTask", "AlignmentInitTask", "VariantProcessingScatterRemotePrepareTask").contains(activeTask)) @@ -610,7 +610,7 @@ private Long getFileSize(PipelineJob job) } @Override - public void processJavaOpts(PipelineJob job, RemoteExecutionEngine engine, @NotNull List existingJavaOpts) + public void processJavaOpts(PipelineJob job, RemoteExecutionEngine> engine, @NotNull List existingJavaOpts) { if (job instanceof HasJobParams) { diff --git a/tcrdb/resources/queries/tcrdb/clone_responses/.qview.xml b/tcrdb/resources/queries/tcrdb/clone_responses/.qview.xml new file mode 100644 index 000000000..d07972bdb --- /dev/null +++ b/tcrdb/resources/queries/tcrdb/clone_responses/.qview.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tcrdb/resources/queries/tcrdb/clone_responses/Cohort Info.qview.xml b/tcrdb/resources/queries/tcrdb/clone_responses/Cohort Info.qview.xml new file mode 100644 index 000000000..4e7d72c87 --- /dev/null +++ b/tcrdb/resources/queries/tcrdb/clone_responses/Cohort Info.qview.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tcrdb/resources/queries/tcrdb/stims/.qview.xml b/tcrdb/resources/queries/tcrdb/stims/.qview.xml index 65170b8d8..e00aaa911 100644 --- a/tcrdb/resources/queries/tcrdb/stims/.qview.xml +++ b/tcrdb/resources/queries/tcrdb/stims/.qview.xml @@ -11,6 +11,7 @@ + diff --git a/tcrdb/resources/queries/tcrdb/stims/Cohort Info.qview.xml b/tcrdb/resources/queries/tcrdb/stims/Cohort Info.qview.xml new file mode 100644 index 000000000..2cf6da768 --- /dev/null +++ b/tcrdb/resources/queries/tcrdb/stims/Cohort Info.qview.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tcrdb/resources/schemas/dbscripts/postgresql/tcrdb-15.54-15.55.sql b/tcrdb/resources/schemas/dbscripts/postgresql/tcrdb-15.54-15.55.sql new file mode 100644 index 000000000..db2c19102 --- /dev/null +++ b/tcrdb/resources/schemas/dbscripts/postgresql/tcrdb-15.54-15.55.sql @@ -0,0 +1,7 @@ +ALTER TABLE tcrdb.clone_responses ADD vGene varchar(1000); +ALTER TABLE tcrdb.clone_responses ADD totalCells int; +ALTER TABLE tcrdb.clone_responses ADD totalCloneSize int; +ALTER TABLE tcrdb.clone_responses ADD fractionCloneActivated double precision; +ALTER TABLE tcrdb.clone_responses ADD totalCellsForSample int; +ALTER TABLE tcrdb.clone_responses ADD oddsRatio double precision; +ALTER TABLE tcrdb.clone_responses ADD enrichmentFDR double precision; diff --git a/tcrdb/resources/schemas/dbscripts/postgresql/tcrdb-15.55-15.56.sql b/tcrdb/resources/schemas/dbscripts/postgresql/tcrdb-15.55-15.56.sql new file mode 100644 index 000000000..b42680c3d --- /dev/null +++ b/tcrdb/resources/schemas/dbscripts/postgresql/tcrdb-15.55-15.56.sql @@ -0,0 +1 @@ +ALTER TABLE tcrdb.clone_responses ADD jGene varchar(1000); diff --git a/tcrdb/resources/schemas/dbscripts/postgresql/tcrdb-15.56-15.57.sql b/tcrdb/resources/schemas/dbscripts/postgresql/tcrdb-15.56-15.57.sql new file mode 100644 index 000000000..21758fc7e --- /dev/null +++ b/tcrdb/resources/schemas/dbscripts/postgresql/tcrdb-15.56-15.57.sql @@ -0,0 +1 @@ +ALTER TABLE tcrdb.stims ADD nClones int; diff --git a/tcrdb/resources/schemas/dbscripts/sqlserver/tcrdb-15.54-15.55.sql b/tcrdb/resources/schemas/dbscripts/sqlserver/tcrdb-15.54-15.55.sql new file mode 100644 index 000000000..db2c19102 --- /dev/null +++ b/tcrdb/resources/schemas/dbscripts/sqlserver/tcrdb-15.54-15.55.sql @@ -0,0 +1,7 @@ +ALTER TABLE tcrdb.clone_responses ADD vGene varchar(1000); +ALTER TABLE tcrdb.clone_responses ADD totalCells int; +ALTER TABLE tcrdb.clone_responses ADD totalCloneSize int; +ALTER TABLE tcrdb.clone_responses ADD fractionCloneActivated double precision; +ALTER TABLE tcrdb.clone_responses ADD totalCellsForSample int; +ALTER TABLE tcrdb.clone_responses ADD oddsRatio double precision; +ALTER TABLE tcrdb.clone_responses ADD enrichmentFDR double precision; diff --git a/tcrdb/resources/schemas/dbscripts/sqlserver/tcrdb-15.55-15.56.sql b/tcrdb/resources/schemas/dbscripts/sqlserver/tcrdb-15.55-15.56.sql new file mode 100644 index 000000000..b42680c3d --- /dev/null +++ b/tcrdb/resources/schemas/dbscripts/sqlserver/tcrdb-15.55-15.56.sql @@ -0,0 +1 @@ +ALTER TABLE tcrdb.clone_responses ADD jGene varchar(1000); diff --git a/tcrdb/resources/schemas/dbscripts/sqlserver/tcrdb-15.56-15.57.sql b/tcrdb/resources/schemas/dbscripts/sqlserver/tcrdb-15.56-15.57.sql new file mode 100644 index 000000000..21758fc7e --- /dev/null +++ b/tcrdb/resources/schemas/dbscripts/sqlserver/tcrdb-15.56-15.57.sql @@ -0,0 +1 @@ +ALTER TABLE tcrdb.stims ADD nClones int; diff --git a/tcrdb/resources/schemas/tcrdb.xml b/tcrdb/resources/schemas/tcrdb.xml index 88951dd56..60a93c0ad 100644 --- a/tcrdb/resources/schemas/tcrdb.xml +++ b/tcrdb/resources/schemas/tcrdb.xml @@ -134,6 +134,30 @@ Background Freq + + V-Gene + + + J-Gene + + + Total Cells + + + Total Clone Size + + + Fraction of Clone Activated + + + Total Cells For Sample + + + Enrichment (Odds Ratio) + + + Enrichment (FDR) + @@ -296,6 +320,9 @@ Flow Quantification + + # Clones + diff --git a/tcrdb/src/org/labkey/tcrdb/TCRdbModule.java b/tcrdb/src/org/labkey/tcrdb/TCRdbModule.java index dd7b9d912..d452b218b 100644 --- a/tcrdb/src/org/labkey/tcrdb/TCRdbModule.java +++ b/tcrdb/src/org/labkey/tcrdb/TCRdbModule.java @@ -46,7 +46,7 @@ public String getName() @Override public Double getSchemaVersion() { - return 15.54; + return 15.57; } @Override