Skip to content

Commit

Permalink
TN-3236 track number of consecutive hard triggers for each domain (#4334
Browse files Browse the repository at this point in the history
)
  • Loading branch information
pbugni authored Aug 14, 2023
1 parent 2730ba2 commit 13cf790
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 11 deletions.
84 changes: 84 additions & 0 deletions portal/migrations/versions/80c3b1e96c45_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"""Add sequential hard trigger count to EMPRO trigger_states.triggers domains.
Revision ID: 80c3b1e96c45
Revises: 2e9b9e696bb8
Create Date: 2023-07-24 17:08:35.128975
"""
from collections import defaultdict
from copy import deepcopy
from alembic import op
from io import StringIO
from sqlalchemy.orm import sessionmaker
from portal.database import db
from portal.trigger_states.empro_domains import (
EMPRO_DOMAINS,
sequential_hard_trigger_count_key,
)
from portal.trigger_states.models import TriggerState

# revision identifiers, used by Alembic.
revision = '80c3b1e96c45'
down_revision = '2e9b9e696bb8'

Session = sessionmaker()


def upgrade():
# for each active EMPRO patient with at least 1 hard triggered domain,
# walk through their monthly reports, adding the sequential count for
# the opt-out feature.
bind = op.get_bind()
session = Session(bind=bind)

patient_ids = []
for patient_id in session.execute(
"SELECT DISTINCT(user_id) FROM trigger_states JOIN users"
" ON users.id = user_id WHERE deleted_id IS NULL"):
patient_ids.append(patient_id[0])

output = StringIO()
for pid in patient_ids:
# can't just send through current process, as it'll attempt to
# insert undesired rows in the trigger_states table. need to
# add the sequential count to existing rows.
output.write(f"\n\nPatient: {pid} storing all zeros for sequential hard triggers except:\n")
output.write(" (visit month : domain : # hard sequential)\n")
sequential_by_domain = defaultdict(list)
trigger_states = db.session.query(TriggerState).filter(
TriggerState.user_id == pid).filter(
TriggerState.state == "resolved").order_by(
TriggerState.timestamp.asc())
for ts in trigger_states:
improved_triggers = deepcopy(ts.triggers)
for d in EMPRO_DOMAINS:
sequential_hard_for_this_domain = 0
if d not in improved_triggers["domain"]:
# only seen on test, fill in the missing domain
print(f"missing {d} in {pid}:{ts.visit_month}?")
improved_triggers["domain"][d] = {}

if any(v == "hard" for v in improved_triggers["domain"][d].values()):
sequential_by_domain[d].append(ts.visit_month)
for i in range(ts.visit_month, -1, -1):
if i not in sequential_by_domain[d]:
break # any break in sequential months, we start over
sequential_hard_for_this_domain += 1
improved_triggers["domain"][d][
sequential_hard_trigger_count_key] = sequential_hard_for_this_domain
if sequential_hard_for_this_domain > 0:
output.write(f" {ts.visit_month}:{d}: {improved_triggers['domain'][d][sequential_hard_trigger_count_key]}\n")

# retain triggers now containing sequential counts
ts.triggers = improved_triggers

output.write(f" patient's list, by domain, of visit months w/ hard triggers:\n ")
for k, v in sequential_by_domain.items():
output.write(f"{k}: {v}; ")

db.session.commit()
print(output.getvalue())


def downgrade():
pass # no value in removing
17 changes: 17 additions & 0 deletions portal/trigger_states/empro_domains.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
'general_pain', 'joint_pain', 'insomnia', 'fatigue', 'anxious',
'discouraged', 'sad', 'social_isolation')

sequential_hard_trigger_count_key = '_sequential_hard_trigger_count'


class AnswerIdValue(object):
"""Simple container to hold identifier and value for an answer"""
Expand Down Expand Up @@ -45,6 +47,10 @@ def check_for_worsening(self, previous, current):
"""Helper to look for worsening conditions"""
keyset = set(list(previous.keys()) + list(current.keys()))
for link_id in keyset:
# ignore metadata keys such as '_sequential_hard_trigger_count'
if link_id.startswith('_'):
continue

if link_id not in previous or link_id not in current:
# Without an answer in both, can't compare
continue
Expand Down Expand Up @@ -80,6 +86,17 @@ def eval(self):
self.check_for_worsening(
self.initial_answers, self.current_answers)

# track number of sequential hard triggers for domain
sequential_hard_trigger_count = 0
if any(self._triggers[k] == 'hard' for k in self._triggers.keys()):
sequential_hard_trigger_count = 1
if (
sequential_hard_trigger_count and
self.previous_answers and
sequential_hard_trigger_count_key in self.previous_answers):
sequential_hard_trigger_count = self.previous_answers[sequential_hard_trigger_count_key] + 1
self._triggers[sequential_hard_trigger_count_key] = sequential_hard_trigger_count


class DomainManifold(object):
"""Bring together available responses and domains for trigger eval"""
Expand Down
28 changes: 17 additions & 11 deletions tests/test_trigger_states.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@

from portal.database import db
from portal.models.audit import Audit
from portal.trigger_states.empro_domains import DomainTriggers
from portal.trigger_states.empro_domains import (
DomainTriggers,
sequential_hard_trigger_count_key,
)
from portal.trigger_states.empro_states import (
EMPRO_state,
evaluate_triggers,
Expand Down Expand Up @@ -127,13 +130,14 @@ def test_cur_hard_trigger():
dt = DomainTriggers(
domain='anxious',
current_answers={
'ironman_ss.12': (3, None),
'ironman_ss.11': (2, None),
'ironman_ss.13': (4, 'penultimate')},
'ironman_ss.12': ('3', None),
'ironman_ss.11': ('2', None),
'ironman_ss.13': ('4', 'penultimate')},
previous_answers=None,
initial_answers=None)
assert len(dt.triggers) == 1
assert len([k for k in dt.triggers.keys() if not k.startswith('_')]) == 1
assert 'ironman_ss.13' in dt.triggers
assert dt.triggers[sequential_hard_trigger_count_key] == 1


def test_worsening_soft_trigger():
Expand All @@ -144,23 +148,25 @@ def test_worsening_soft_trigger():
current_answers={
'ss.15': (3, None), 'ss.12': (3, None), 'ss.21': (1, None)},
initial_answers=None)
assert len(dt.triggers) == 1
assert len([k for k in dt.triggers.keys() if not k.startswith('_')]) == 1
assert dt.triggers['ss.15'] == 'soft'
assert dt.triggers[sequential_hard_trigger_count_key] == 0


def test_worsening_baseline():
# confirm a hard trigger with 2 level worsening
initial_answers = {15: (3, None), 21: (1, None)}
previous_answers = {12: (1, None), 15: (3, None)}
current_answers = {12: (3, None), 15: (3, None), 21: (3, None)}
initial_answers = {'15': (3, None), '21': (1, None)}
previous_answers = {'12': (1, None), '15': (3, None)}
current_answers = {'12': (3, None), '15': (3, None), '21': (3, None)}
dt = DomainTriggers(
domain='anxious',
initial_answers=initial_answers,
previous_answers=previous_answers,
current_answers=current_answers)

assert len(dt.triggers) == 2
assert dt.triggers[12] == dt.triggers[21] == 'hard'
assert len([k for k in dt.triggers.keys() if not k.startswith('_')]) == 2
assert dt.triggers['12'] == dt.triggers['21'] == 'hard'
assert dt.triggers[sequential_hard_trigger_count_key] == 1


def test_ts_trigger_lists(mock_triggers):
Expand Down

0 comments on commit 13cf790

Please sign in to comment.