-- THIS SCRIPT IS AUTOMATICALLY GENERATED. DO NOT EDIT IT DIRECTLY.
DROP TABLE IF EXISTS weight_durations; CREATE TABLE weight_durations AS 
-- This query extracts weights for adult ICU patients with start/stop times
-- if an admission weight is given, then this is assigned from intime to outtime

-- This query extracts weights for adult ICU patients with start/stop times
-- if an admission weight is given, then this is assigned from intime to outtime
WITH wt_neonate AS
( 
    SELECT c.icustay_id, c.charttime
    , MAX(CASE WHEN c.itemid = 3580 THEN c.valuenum END) as wt_kg
    , MAX(CASE WHEN c.itemid = 3581 THEN c.valuenum END) as wt_lb
    , MAX(CASE WHEN c.itemid = 3582 THEN c.valuenum END) as wt_oz
    FROM chartevents c
    WHERE c.itemid in (3580, 3581, 3582)
    AND c.icustay_id IS NOT NULL
    AND COALESCE(c.error, 0) = 0
    -- wt_oz/wt_lb/wt_kg are only 0 erroneously, so drop these rows
    AND c.valuenum > 0
  -- a separate query was run to manually verify only 1 value exists per
  -- icustay_id/charttime/itemid grouping
  -- therefore, we can use max() across itemid to collapse these values to 1 row per group
    GROUP BY c.icustay_id, c.charttime
)
, birth_wt AS
(
    SELECT c.icustay_id, c.charttime
    , MAX(
      CASE
      WHEN c.itemid = 4183 THEN
        -- clean free-text birth weight data
        CASE
          -- ignore value if there are any non-numeric characters
          WHEN REGEXP_CONTAINS(c.value, '[^0-9\\.]') THEN NULL 
          -- convert grams to kd
          WHEN CAST(c.value AS NUMERIC) > 100 THEN CAST(c.value AS NUMERIC)/1000
          -- keep kg as is, filtering bad values (largest baby ever born was conveniently 9.98kg)
          WHEN CAST(c.value AS NUMERIC) < 10 THEN CAST(c.value AS NUMERIC)
          -- ignore other values (those between 10-100) - junk data
        ELSE NULL END
      -- itemid 3723 happily has all numeric data - also doesn't store any grams data
      WHEN c.itemid = 3723 AND c.valuenum < 10 THEN c.valuenum
      ELSE NULL END) as wt_kg
    FROM chartevents c
    WHERE c.itemid in (3723, 4183)
    AND c.icustay_id IS NOT NULL
    AND COALESCE(c.error, 0) = 0
  -- a separate query was run to manually verify only 1 value exists per
  -- icustay_id/charttime/itemid grouping
  -- therefore, we can use max() across itemid to collapse these values to 1 row per group
    GROUP BY c.icustay_id, c.charttime
)
, wt_stg as
(
    SELECT
        c.icustay_id
      , c.charttime
      , case when c.itemid in (762,226512) then 'admit'
          else 'daily' end as weight_type
      -- TODO: eliminate obvious outliers if there is a reasonable weight
      , c.valuenum as weight
    FROM chartevents c
    WHERE c.valuenum IS NOT NULL
      AND c.itemid in
      (
          762,226512 -- Admit Wt
        , 763,224639 -- Daily Weight
      )
      AND c.icustay_id IS NOT NULL
      AND c.valuenum > 0
      -- exclude rows marked as error
      AND COALESCE(c.error, 0) = 0
    UNION ALL
    SELECT
        n.icustay_id
      , n.charttime
      , 'daily' AS weight_type
      , CASE
          WHEN wt_kg IS NOT NULL THEN wt_kg
          WHEN wt_lb IS NOT NULL THEN wt_lb*0.45359237 + wt_oz*0.0283495231
        ELSE NULL END AS weight
    FROM wt_neonate n
    UNION ALL
    SELECT
        b.icustay_id
      , b.charttime
      -- birth weight of neonates is treated as admission weight
      , 'admit' AS weight_type
      , wt_kg as weight
    FROM birth_wt b
)
-- get more weights from echo - completes data for ~2500 patients
-- we only use echo data if there is *no* charted data
-- we impute the median echo weight for their entire ICU stay
, echo as
(
  select
    ie.icustay_id
    , ec.charttime
    , 'echo' AS weight_type
    , 0.453592*ec.weight as weight
  from icustays ie
  inner join echo_data ec
    on ie.hadm_id = ec.hadm_id
  where ec.weight is not null
  and ie.icustay_id not in (select distinct icustay_id from wt_stg)
)
, wt_stg0 AS
(
  SELECT icustay_id, charttime, weight_type, weight
  FROM wt_stg
  UNION ALL
  SELECT icustay_id, charttime, weight_type, weight
  FROM echo
)
-- assign ascending row number
, wt_stg1 as
(
  select
      icustay_id
    , charttime
    , weight_type
    , weight
    , ROW_NUMBER() OVER (partition by icustay_id, weight_type order by charttime) as rn
  from wt_stg0
  WHERE weight IS NOT NULL
)
-- change charttime to intime for the first admission weight recorded
, wt_stg2 AS
(
  SELECT 
      wt_stg1.icustay_id
    , ie.intime, ie.outtime
    , case when wt_stg1.weight_type = 'admit' and wt_stg1.rn = 1
        then DATETIME_SUB(ie.intime, INTERVAL '2' HOUR)
      else wt_stg1.charttime end as starttime
    , wt_stg1.weight
  from wt_stg1
  INNER JOIN icustays ie
    on ie.icustay_id = wt_stg1.icustay_id
)
, wt_stg3 as
(
  select
    icustay_id
    , intime, outtime
    , starttime
    , coalesce(
        LEAD(starttime) OVER (PARTITION BY icustay_id ORDER BY starttime),
        DATETIME_ADD(GREATEST(outtime, starttime), INTERVAL '2' HOUR)
      ) as endtime
    , weight
  from wt_stg2
)
-- this table is the start/stop times from admit/daily weight in charted data
, wt1 as
(
  select
      icustay_id
    , starttime
    , coalesce(endtime,
      LEAD(starttime) OVER (partition by icustay_id order by starttime),
      -- impute ICU discharge as the end of the final weight measurement
      -- plus a 2 hour "fuzziness" window
      DATETIME_ADD(outtime, INTERVAL '2' HOUR)
    ) as endtime
    , weight
  from wt_stg3
)
-- if the intime for the patient is < the first charted daily weight
-- then we will have a "gap" at the start of their stay
-- to prevent this, we look for these gaps and backfill the first weight
-- this adds (153255-149657)=3598 rows, meaning this fix helps for up to 3598 icustay_id
, wt_fix as
(
  select ie.icustay_id
    -- we add a 2 hour "fuzziness" window
    , DATETIME_SUB(ie.intime, INTERVAL '2' HOUR) as starttime
    , wt.starttime as endtime
    , wt.weight
  from icustays ie
  inner join
  -- the below subquery returns one row for each unique icustay_id
  -- the row contains: the first starttime and the corresponding weight
  (
    SELECT wt1.icustay_id, wt1.starttime, wt1.weight
    , ROW_NUMBER() OVER (PARTITION BY wt1.icustay_id ORDER BY wt1.starttime) as rn
    FROM wt1
  ) wt
    ON  ie.icustay_id = wt.icustay_id
    AND wt.rn = 1
    and ie.intime < wt.starttime
)
-- add the backfill rows to the main weight table
select
    wt1.icustay_id
  , wt1.starttime
  , wt1.endtime
  , wt1.weight
from wt1
UNION ALL
SELECT
    wt_fix.icustay_id
  , wt_fix.starttime
  , wt_fix.endtime
  , wt_fix.weight
from wt_fix
