From 851cf44358ecb59a75dfbc902b81433699f8977b Mon Sep 17 00:00:00 2001 From: pbugni Date: Tue, 12 Mar 2024 08:32:51 -0700 Subject: [PATCH 001/143] bug found in qb_timeline overlap check (#4368) between protocol v3 and v5, additional months were added, i.e. month 33. if v3 month 36 didn't have any committed work and it's time to change protocols for the user, v5 month 33 should follow v3 month 30. the code looking for this case had an extra check which prevented the above from functioning, in the rare case where v3 month 30 ended before v5 month 33 starts. this was discovered with the new withdrawal handling in adherence data, as the insertion of the withdrawal row could not predictably insert before the time point just after withdrawal, when the timeline was found to have such an overlap. --- portal/models/qb_timeline.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/portal/models/qb_timeline.py b/portal/models/qb_timeline.py index aee9822af..c263fb381 100644 --- a/portal/models/qb_timeline.py +++ b/portal/models/qb_timeline.py @@ -812,7 +812,11 @@ def int_or_none(value): for row in qbt_rows: # Confirm expected order if last_at: - assert row.at >= last_at + if last_at > row.at: + raise ValueError( + f"patient {row.user_id} has overlapping qb_timeline rows" + f" {last_at} and {row.at}" + ) key = f"{row.qb_id}:{row.qb_iteration}" if previous_key and previous_key != key: @@ -975,11 +979,9 @@ def attempt_update(user_id, research_study_id, invalidate_existing): if ( pending_qbts[j].qb_id != remove_qb_id or pending_qbts[j].qb_iteration != remove_iteration): - # To qualify for this special case, - # having worked back to previous QB, if - # at > start, take action - if pending_qbts[j].at > start: - unwanted_count = len(pending_qbts)-j-1 + # unwanted_count represents all rows from + # overlapped, unwanted visit + unwanted_count = len(pending_qbts)-j-1 break # keep a lookout for work done in old RP From f75d14c4efa4c4e2041eb4b220fae6299470c44b Mon Sep 17 00:00:00 2001 From: Ivan Cvitkovic Date: Wed, 13 Mar 2024 17:04:04 -0700 Subject: [PATCH 002/143] Increase max line length for linters (#4369) - Increase max line length allowed by linting tools [Some precedents established by others](https://knox.codes/posts/line-length-limits) --- .editorconfig | 2 +- .pep8speaks.yml | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.editorconfig b/.editorconfig index b085af901..5235b56bb 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,7 +3,7 @@ [*.py] # isort settings # https://github.com/timothycrosley/isort/wiki/isort-Settings -line_length=79 +line_length=100 multi_line_output=3 include_trailing_comma=True known_first_party=tests diff --git a/.pep8speaks.yml b/.pep8speaks.yml index 0d79f96fd..d589dd0b5 100644 --- a/.pep8speaks.yml +++ b/.pep8speaks.yml @@ -2,8 +2,7 @@ # https://pep8speaks.com/ pycodestyle: - # Default is 79 in PEP8 - max-line-length: 79 + max-line-length: 100 scanner: # Errors caused by only the patch are shown, not the whole file From 17931d2ef40fd6c381e149c004df01616e8c403b Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Wed, 6 Sep 2023 15:53:38 -0700 Subject: [PATCH 003/143] TN-3236, sequential hard trigger counts kept in trigger_states.triggers, not previous answers! --- portal/trigger_states/empro_domains.py | 17 +++++++++++------ portal/trigger_states/empro_states.py | 15 ++++++++++----- tests/test_trigger_states.py | 20 ++++++++++++++++---- 3 files changed, 37 insertions(+), 15 deletions(-) diff --git a/portal/trigger_states/empro_domains.py b/portal/trigger_states/empro_domains.py index d60b69d91..d5167c4b4 100644 --- a/portal/trigger_states/empro_domains.py +++ b/portal/trigger_states/empro_domains.py @@ -28,7 +28,7 @@ class DomainTriggers(object): """ def __init__( - self, domain, current_answers, previous_answers, initial_answers): + self, domain, current_answers, previous_answers, initial_answers, previous_triggers): self.domain = domain self._triggers = dict() @@ -38,6 +38,9 @@ def __init__( self.previous_answers = previous_answers or dict() self.initial_answers = initial_answers or dict() + # Trigger state triggers from previous month, if defined + self.previous_triggers = previous_triggers + @property def triggers(self): self.eval() @@ -92,9 +95,9 @@ def eval(self): 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.previous_triggers and + sequential_hard_trigger_count_key in self.previous_triggers): + sequential_hard_trigger_count = self.previous_triggers[sequential_hard_trigger_count_key] + 1 self._triggers[sequential_hard_trigger_count_key] = sequential_hard_trigger_count @@ -145,17 +148,19 @@ def obtain_observations(self, qnr): results[domain][link_id] = (int(score), severity) setattr(self, f"{timepoint}_obs", results) - def eval_triggers(self): + def eval_triggers(self, previous_triggers): triggers = dict() triggers['domain'] = dict() for domain in EMPRO_DOMAINS: if domain in self.cur_obs: + prev_triggers_for_domain = previous_triggers["domain"][domain] if previous_triggers else None dt = DomainTriggers( domain=domain, current_answers=self.cur_obs[domain], previous_answers=self.prev_obs.get(domain), - initial_answers=self.initial_obs.get(domain) + initial_answers=self.initial_obs.get(domain), + previous_triggers=prev_triggers_for_domain, ) triggers['domain'][domain] = dt.triggers diff --git a/portal/trigger_states/empro_states.py b/portal/trigger_states/empro_states.py index a2c73e52f..4ac05204f 100644 --- a/portal/trigger_states/empro_states.py +++ b/portal/trigger_states/empro_states.py @@ -210,9 +210,18 @@ def evaluate_triggers(qnr): ts = users_trigger_state(qnr.subject_id) sm = EMPRO_state(ts) + # include previous month resolved row, if available + previous = TriggerState.query.filter( + TriggerState.user_id == qnr.subject_id).filter( + TriggerState.state == 'resolved').order_by( + TriggerState.timestamp.desc()).first() + # bring together and evaluate available data for triggers dm = DomainManifold(qnr) - ts.triggers = dm.eval_triggers() + previous_triggers = ( + previous if previous and previous.visit_month + 1 == ts.visit_month + else None) + ts.triggers = dm.eval_triggers(previous_triggers) ts.questionnaire_response_id = qnr.id # transition and persist state @@ -225,10 +234,6 @@ def evaluate_triggers(qnr): # a submission closes the window of availability for the # post-intervention clinician follow up. mark state if # one is found - previous = TriggerState.query.filter( - TriggerState.user_id == qnr.subject_id).filter( - TriggerState.state == 'resolved').order_by( - TriggerState.timestamp.desc()).first() if previous and previous.triggers.get('action_state') not in ( 'completed', 'missed', 'not applicable', 'withdrawn'): triggers = copy.deepcopy(previous.triggers) diff --git a/tests/test_trigger_states.py b/tests/test_trigger_states.py index d15b060b3..de471e5b4 100644 --- a/tests/test_trigger_states.py +++ b/tests/test_trigger_states.py @@ -127,6 +127,15 @@ def test_2nd_eval( def test_cur_hard_trigger(): # Single result with a severe should generate a hard (and soft) trigger + + # include a previous triggered state to test sequential count + previous_triggers = { + "ironman_ss.11": "hard", + "ironman_ss.12": "hard", + "ironman_ss.13": "hard", + "_sequential_hard_trigger_count": 3, + } + dt = DomainTriggers( domain='anxious', current_answers={ @@ -134,10 +143,11 @@ def test_cur_hard_trigger(): 'ironman_ss.11': ('2', None), 'ironman_ss.13': ('4', 'penultimate')}, previous_answers=None, - initial_answers=None) + initial_answers=None, + previous_triggers=previous_triggers) 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 + assert dt.triggers[sequential_hard_trigger_count_key] == 4 def test_worsening_soft_trigger(): @@ -147,7 +157,8 @@ def test_worsening_soft_trigger(): previous_answers={'ss.21': (2, None), 'ss.15': (2, None)}, current_answers={ 'ss.15': (3, None), 'ss.12': (3, None), 'ss.21': (1, None)}, - initial_answers=None) + initial_answers=None, + previous_triggers=None) 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 @@ -162,7 +173,8 @@ def test_worsening_baseline(): domain='anxious', initial_answers=initial_answers, previous_answers=previous_answers, - current_answers=current_answers) + current_answers=current_answers, + previous_triggers=None) assert len([k for k in dt.triggers.keys() if not k.startswith('_')]) == 2 assert dt.triggers['12'] == dt.triggers['21'] == 'hard' From 2d06578be5d7f1d28966e8d277009e3656a0c7b7 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Wed, 20 Sep 2023 14:49:29 -0700 Subject: [PATCH 004/143] expand migration downgrade step, to remove all sequential hard domain counts, so the test db can be switched over to a hotfix without this migration. --- portal/migrations/versions/80c3b1e96c45_.py | 31 ++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/portal/migrations/versions/80c3b1e96c45_.py b/portal/migrations/versions/80c3b1e96c45_.py index fee8ec078..304fdc509 100644 --- a/portal/migrations/versions/80c3b1e96c45_.py +++ b/portal/migrations/versions/80c3b1e96c45_.py @@ -81,4 +81,33 @@ def upgrade(): def downgrade(): - pass # no value in removing + # for each active EMPRO patient with at least 1 hard triggered domain, + # remove any sequential counts found + 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: + output.write(f"\n\nPatient: {pid}\n") + 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: + if sequential_hard_trigger_count_key in improved_triggers["domain"][d]: + del improved_triggers["domain"][d][sequential_hard_trigger_count_key] + output.write(f" removed sequential from {ts.visit_month}:{d} {improved_triggers['domain'][d]}\n") + + # retain triggers now containing sequential counts + ts.triggers = improved_triggers + + db.session.commit() + print(output.getvalue()) From 4e6912bc752a7f6b2da2a13e91771e1a8557cc04 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Thu, 16 Nov 2023 17:14:38 -0800 Subject: [PATCH 005/143] add debugging into to exception text --- portal/models/qb_timeline.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/portal/models/qb_timeline.py b/portal/models/qb_timeline.py index c263fb381..b4fb8de66 100644 --- a/portal/models/qb_timeline.py +++ b/portal/models/qb_timeline.py @@ -374,7 +374,8 @@ def qbds_for_rp(rp, classification, trigger_date): ) if curRPD.retired == nextRPD.retired: raise ValueError( - "Invalid state: multiple RPs w/ same retire date") + "Invalid state: multiple RPs w/ same retire date: " + f"{next_rp} : {curRPD.retired}") else: nextRPD = None yield curRPD, nextRPD From 075506e783af3b69a216beef7c09f054b18aa6bf Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Wed, 29 Nov 2023 15:26:57 -0800 Subject: [PATCH 006/143] add exception to catch details for bogus records --- portal/migrations/versions/80c3b1e96c45_.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/portal/migrations/versions/80c3b1e96c45_.py b/portal/migrations/versions/80c3b1e96c45_.py index 304fdc509..d6a6bf41d 100644 --- a/portal/migrations/versions/80c3b1e96c45_.py +++ b/portal/migrations/versions/80c3b1e96c45_.py @@ -102,6 +102,8 @@ def downgrade(): for ts in trigger_states: improved_triggers = deepcopy(ts.triggers) for d in EMPRO_DOMAINS: + if d not in improved_triggers["domain"]: + raise RuntimeError(f"{d} missing from {ts.visit_month} for {patient_id}") if sequential_hard_trigger_count_key in improved_triggers["domain"][d]: del improved_triggers["domain"][d][sequential_hard_trigger_count_key] output.write(f" removed sequential from {ts.visit_month}:{d} {improved_triggers['domain'][d]}\n") From ea274796d3d21a9f67ac6a29c65970e1fa098eeb Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Wed, 29 Nov 2023 15:27:59 -0800 Subject: [PATCH 007/143] add `sanity_check()` to /timewarp - confirm invariants before and after moving patient timeline. --- portal/views/patient.py | 43 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/portal/views/patient.py b/portal/views/patient.py index 7ef1e9c16..f4c90f2c5 100644 --- a/portal/views/patient.py +++ b/portal/views/patient.py @@ -4,6 +4,7 @@ for staff """ +from collections import defaultdict from datetime import datetime import json @@ -367,6 +368,10 @@ def patient_timeline(patient_id): except ValueError as ve: abort(500, str(ve)) + consents = [ + {"research_study_id": c.research_study_id, + "acceptance_date": c.acceptance_date, + "status": c.status} for c in user.valid_consents] results = [] # We order by at (to get the latest status for a given QB) and # secondly by id, as on rare occasions, the time (`at`) of @@ -543,7 +548,41 @@ def patient_timewarp(patient_id, days): from copy import deepcopy from portal.models.questionnaire_response import QuestionnaireResponse from portal.models.user_consent import UserConsent - + from ..trigger_states.models import TriggerState + + def sanity_check(): + """confirm user state before / after timewarp""" + # User should have one valid consent + patient = get_user(patient_id, 'view') + consents = patient.valid_consents + rps = [c for c in consents if c.research_study_id == 0] + assert len(rps) == 1 + + rp1s = [c for c in consents if c.research_study_id == 1] + if not len(rp1s): + return + assert len(rp1s) == 1 + + # Confirm valid trigger_states. No data prior to consent. + ts = TriggerState.query.filter( + TriggerState.user_id == patient_id, + TriggerState.timestamp < rp1s[0].acceptance_date).count() + assert ts == 0 + + # should never be more than a single row for any given state + ts = TriggerState.query.filter( + TriggerState.user_id == patient_id + ) + data = defaultdict(int) + for row in ts: + key = f"{row.visit_month}:{row.state}" + data[key] += 1 + for k, v in data.items(): + if v > 1: + raise RuntimeError( + f"Unique visit_month:state {k} broken in trigger_states for {patient}") + + sanity_check() if current_app.config['SYSTEM_TYPE'] == "production": abort(404) @@ -569,7 +608,6 @@ def patient_timewarp(patient_id, days): # trigger_state if current_app.config['GIL'] is None: - from ..trigger_states.models import TriggerState for ts in TriggerState.query.filter( TriggerState.user_id == user.id): changed.append(f"trigger_state {ts.id}") @@ -603,6 +641,7 @@ def patient_timewarp(patient_id, days): ar.timestamp = ar.timestamp - delta db.session.commit() + sanity_check() # Recalculate users timeline & qnr associations cache.delete_memoized(trigger_date) From d9b576f65f9eafdd2a1b1bb915a596f3658d378d Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Wed, 24 Jan 2024 19:25:24 -0800 Subject: [PATCH 008/143] rework alembic migration order to reapply hard trigger migration again --- portal/migrations/versions/d1f3ed8d16ef_.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/portal/migrations/versions/d1f3ed8d16ef_.py b/portal/migrations/versions/d1f3ed8d16ef_.py index 133639bf0..531bc8604 100644 --- a/portal/migrations/versions/d1f3ed8d16ef_.py +++ b/portal/migrations/versions/d1f3ed8d16ef_.py @@ -11,7 +11,7 @@ # revision identifiers, used by Alembic. revision = 'd1f3ed8d16ef' -down_revision = '80c3b1e96c45' +down_revision = '2e9b9e696bb8' def upgrade(): From 62ba2428e481785f0b204057269bf141250c7bc0 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Thu, 25 Jan 2024 15:31:30 -0800 Subject: [PATCH 009/143] refactor to reuse same migration again, with patched code in place --- portal/migrations/versions/80c3b1e96c45_.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/portal/migrations/versions/80c3b1e96c45_.py b/portal/migrations/versions/80c3b1e96c45_.py index d6a6bf41d..3e6ed5d0c 100644 --- a/portal/migrations/versions/80c3b1e96c45_.py +++ b/portal/migrations/versions/80c3b1e96c45_.py @@ -19,12 +19,19 @@ # revision identifiers, used by Alembic. revision = '80c3b1e96c45' -down_revision = '2e9b9e696bb8' +down_revision = '66368e673005' Session = sessionmaker() def upgrade(): + # Add sequential counts to appropriate trigger_states rows. + + # this migration was applied once before, but the code wasn't correctly + # maintaining the sequential counts. start by removing all for a clean + # slate via the same `downgrade()` step + downgrade() + # 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. @@ -103,7 +110,7 @@ def downgrade(): improved_triggers = deepcopy(ts.triggers) for d in EMPRO_DOMAINS: if d not in improved_triggers["domain"]: - raise RuntimeError(f"{d} missing from {ts.visit_month} for {patient_id}") + raise RuntimeError(f"{d} missing from {ts.visit_month} for {pid}") if sequential_hard_trigger_count_key in improved_triggers["domain"][d]: del improved_triggers["domain"][d][sequential_hard_trigger_count_key] output.write(f" removed sequential from {ts.visit_month}:{d} {improved_triggers['domain'][d]}\n") From 8f0bff9676755cff942273c3b14e145617312cbe Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Wed, 13 Mar 2024 16:10:37 -0700 Subject: [PATCH 010/143] refactor email generation to common empro_messages module. --- portal/trigger_states/empro_messages.py | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/portal/trigger_states/empro_messages.py b/portal/trigger_states/empro_messages.py index 0ad1c5015..dbd16521c 100644 --- a/portal/trigger_states/empro_messages.py +++ b/portal/trigger_states/empro_messages.py @@ -2,7 +2,9 @@ from datetime import datetime from flask import current_app, url_for from flask_babel import gettext as _ +from smtplib import SMTPRecipientsRefused +from portal.database import db from portal.models.app_text import MailResource, app_text from portal.models.communication import EmailMessage, load_template_args from portal.models.organization import UserOrganization @@ -11,6 +13,30 @@ from portal.models.qb_status import QB_Status +def invite_email(user): + if not user.email_ready(): + current_app.logger.error(f"{user.id} can't receive EMPRO invite email") + return + args = load_template_args(user=user) + item = MailResource( + app_text("patient invite email IRONMAN EMPRO Study"), + locale_code=user.locale_code, + variables=args) + msg = EmailMessage( + subject=item.subject, + body=item.body, + recipients=user.email, + sender=current_app.config['MAIL_DEFAULT_SENDER'], + user_id=user.id) + try: + msg.send_message() + except SMTPRecipientsRefused as exc: + current_app.logger.error( + "Error sending EMPRO Invite to %s: %s", + user.email, exc) + db.session.add(msg) + + def patient_email(patient, soft_triggers, hard_triggers): """Prepare email for patient, depending on trigger status""" From 04ba85871f751868557521ce1d98749c760f34d3 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Wed, 13 Mar 2024 16:11:24 -0700 Subject: [PATCH 011/143] progress towards regenerating a user's trigger_states table on EMPRO consent change. --- portal/models/qb_status.py | 13 +++-- portal/trigger_states/empro_states.py | 58 +++++++++----------- portal/trigger_states/models.py | 77 +++++++++++++++++++++++++++ portal/views/user.py | 6 +++ 4 files changed, 117 insertions(+), 37 deletions(-) diff --git a/portal/models/qb_status.py b/portal/models/qb_status.py index c6cf1e774..cd6b755c8 100644 --- a/portal/models/qb_status.py +++ b/portal/models/qb_status.py @@ -520,7 +520,8 @@ def warn_on_duplicate_request(self, requested_set): f" {requested_indef} already!") -def patient_research_study_status(patient, ignore_QB_status=False): +def patient_research_study_status( + patient, ignore_QB_status=False, as_of_date=None, skip_initiate=False): """Returns details regarding patient readiness for available studies Wraps complexity of checking multiple QB_Status and ResearchStudy @@ -532,6 +533,8 @@ def patient_research_study_status(patient, ignore_QB_status=False): :param patient: subject to check :param ignore_QB_status: set to prevent recursive call, if used during process of evaluating QB_status. Will restrict results to eligible + :param as_of_date: set to check status at alternative time + :param skip_initiate: set only when rebuilding to avoid state change :returns: dictionary of applicable studies keyed by research_study_id. Each contains a dictionary with keys: - eligible: set True if assigned to research study and pre-requisites @@ -546,7 +549,8 @@ def patient_research_study_status(patient, ignore_QB_status=False): """ from datetime import datetime from .research_study import EMPRO_RS_ID, ResearchStudy - as_of_date = datetime.utcnow() + if as_of_date is None: + as_of_date = datetime.utcnow() results = {} # check studies in required order - first found with pending work @@ -601,7 +605,8 @@ def patient_research_study_status(patient, ignore_QB_status=False): elif rs_status['ready']: # As user may have just entered ready status on EMPRO # move trigger_states.state to due - from ..trigger_states.empro_states import initiate_trigger - initiate_trigger(patient.id) + if not skip_initiate: + from ..trigger_states.empro_states import initiate_trigger + initiate_trigger(patient.id) return results diff --git a/portal/trigger_states/empro_states.py b/portal/trigger_states/empro_states.py index 4ac05204f..52fd0066e 100644 --- a/portal/trigger_states/empro_states.py +++ b/portal/trigger_states/empro_states.py @@ -12,13 +12,10 @@ from statemachine.exceptions import TransitionNotAllowed from .empro_domains import DomainManifold -from .empro_messages import patient_email, staff_emails +from .empro_messages import invite_email, patient_email, staff_emails from .models import TriggerState from ..database import db from ..date_tools import FHIR_datetime -from ..models.app_text import MailResource, app_text -from ..models.communication import load_template_args -from ..models.message import EmailMessage from ..models.qb_status import QB_Status from ..models.qbd import QBD from ..models.questionnaire_bank import QuestionnaireBank @@ -81,7 +78,7 @@ class EMPRO_state(StateMachine): next_available = resolved.to(due) -def users_trigger_state(user_id): +def users_trigger_state(user_id, as_of_date=None): """Obtain the latest trigger state for given user Returns latest TriggerState row for user or creates transient if not @@ -95,11 +92,21 @@ def users_trigger_state(user_id): - triggered: triggers available in TriggerState.triggers attribute """ - ts = TriggerState.query.filter( + if as_of_date is None: + as_of_date = datetime.utcnow() + + rows = TriggerState.query.filter( TriggerState.user_id == user_id).order_by( - TriggerState.timestamp.desc()).first() + TriggerState.timestamp.desc()) + for ts_row in rows: + # most recent with a timestamp prior to as_of_date, in case this is a rebuild + if as_of_date < ts_row.timestamp: + continue + ts = ts_row + break + if not ts: - ts = TriggerState(user_id=user_id, state='unstarted') + ts = TriggerState(user_id=user_id, state='unstarted', timestamp=as_of_date) return ts @@ -115,19 +122,22 @@ def lookup_visit_month(user_id, as_of_date): return one_index - 1 -def initiate_trigger(user_id): +def initiate_trigger(user_id, as_of_date=None, rebuilding=False): """Call when EMPRO becomes available for user or next is due""" + if as_of_date is None: + as_of_date = datetime.utcnow() + ts = users_trigger_state(user_id) if ts.state == 'due': # Possible the user took no action, as in skipped the last month # (or multiple months may have been skipped if time-warping). # If so, the visit_month and timestamp are updated on the last # `due` row that was found above. - visit_month = lookup_visit_month(user_id, datetime.utcnow()) + visit_month = lookup_visit_month(user_id, as_of_date) if ts.visit_month != visit_month: current_app.logger.warn(f"{user_id} skipped EMPRO visit {ts.visit_month}") ts.visit_month = visit_month - ts.timestamp = datetime.utcnow() + ts.timestamp = as_of_date db.session.commit() # Allow idempotent call - skip out if in correct state @@ -143,7 +153,7 @@ def initiate_trigger(user_id): next_visit = int(ts.visit_month) + 1 current_app.logger.debug(f"transition from {ts} to next due") # generate a new ts, to leave resolved record behind - ts = TriggerState(user_id=user_id, state='unstarted') + ts = TriggerState(user_id=user_id, state='unstarted', as_of_date=as_of_date) ts.visit_month = next_visit current_app.logger.debug( "persist-trigger_states-new from initiate_trigger(), " @@ -159,27 +169,9 @@ def initiate_trigger(user_id): "persist-trigger_states-new from initiate_trigger()," f"record historical clause {ts}") - # TN-2863 auto send invite when first available - if ts.visit_month == 0: - user = User.query.get(user_id) - args = load_template_args(user=user) - item = MailResource( - app_text("patient invite email IRONMAN EMPRO Study"), - locale_code=user.locale_code, - variables=args) - msg = EmailMessage( - subject=item.subject, - body=item.body, - recipients=user.email, - sender=current_app.config['MAIL_DEFAULT_SENDER'], - user_id=user_id) - try: - msg.send_message() - except SMTPRecipientsRefused as exc: - current_app.logger.error( - "Error sending EMPRO Invite to %s: %s", - user.email, exc) - db.session.add(msg) + # TN-2863 auto send invite when first available, unless rebuilding + if ts.visit_month == 0 and not rebuilding: + invite_email(User.query.get(user_id)) db.session.commit() return ts diff --git a/portal/trigger_states/models.py b/portal/trigger_states/models.py index c91402fcb..5934a9c79 100644 --- a/portal/trigger_states/models.py +++ b/portal/trigger_states/models.py @@ -1,4 +1,5 @@ from datetime import datetime, timedelta +from flask import current_app from sqlalchemy.dialects.postgresql import ENUM, JSONB from sqlalchemy.orm import make_transient @@ -254,3 +255,79 @@ def soft_triggers_for_visit(self, visit_month): ts = self.latest_by_visit[visit_month] if ts: return ts.soft_trigger_list() + + +def rebuild_trigger_states(patient): + """If a user's consent moves, need to re-build the trigger states for user + + Especially messy process, as much of the data lives in the trigger_states + table alone, and a consent change may modify start eligibility, etc. + """ + from .empro_states import initiate_trigger + from ..models.overall_status import OverallStatus + from ..models.qb_status import patient_research_study_status + from ..models.qb_timeline import QBT, update_users_QBT + from ..models.research_study import BASE_RS_ID, EMPRO_RS_ID + + # Use the timeline data for accurate start dates, etc. + update_users_QBT(user_id=patient.id, research_study_id=EMPRO_RS_ID) + tl_query = QBT.query.filter(QBT.user_id == patient.id).filter( + QBT.research_study_id == EMPRO_RS_ID).order_by(QBT.id) + if not tl_query.count(): + # User has no timeline data for EMPRO, likely not eligible + if TriggerState.query.filter(TriggerState.user_id == patient.id).count(): + current_app.logging.error(f"no EMPRO timeline, yet trigger_states rows for {patient.id}") + return + + # Capture state in memory for potential reuse when rebuilding + data = [] + for row in TriggerState.query.filter( + TriggerState.user_id == patient.id).order_by(TriggerState.id): + data.append({ + 'id': row.id, + 'state': row.state, + 'timestamp': row.timestamp, + 'questionnaire_response_id': row.questionnaire_response_id, + 'triggers': row.triggers, + 'visit_month': row.visit_month, + }) + + if not data: + # no trigger state data to move, no problem. + return + + # purge rows and rebuild below + # TODO TriggerState.delete and rebuild + raise NotImplemented(f"can not adjust trigger_states for {patient.id}") + + if len([c for c in patient.clinicians]) == 0: + # No valid trigger states without a clinician + return + + visit_month = -1 + for row in tl_query: + if row.status == OverallStatus.due: + # reset any state for next visit: + visit_month += 1 + conclude_as_expired = False + + # 'due' row starts when they became eligible + as_of_date = row.at + study_status = patient_research_study_status( + patient=patient, as_of_date=as_of_date, skip_initiate=True) + + if study_status[BASE_RS_ID]['ready']: + # user had unfinished global work at said point in time. + # check if they complete before the current visit expires + basestudy_query = QBT.query.filter(QBT.user_id == patient.id).filter( + QBT.research_study_id == BASE_RS_ID).filter( + QBT.at.between(as_of_date, as_of_date+timedelta(days=30))).filter( + QBT.status == OverallStatus.completed).first() + if basestudy_query: + # user finished global work on time, start the visit then + as_of_date = basestudy_query.as_of_date + else: + # user was never able to submit visit for given empro month. + # stick with initial start date and let it resolve as expired + conclude_as_expired = True + initiate_trigger(patient.id, as_of_date=as_of_date, rebuilding=True) diff --git a/portal/views/user.py b/portal/views/user.py index 5acce26ea..87c33460f 100644 --- a/portal/views/user.py +++ b/portal/views/user.py @@ -749,6 +749,12 @@ def set_user_consents(user_id): # status - invalidate this user's data at this time. invalidate_users_QBT( user_id=user.id, research_study_id=consent.research_study_id) + + # If user has submitted EMPRO - those must also be recalculated + if consent.research_study_id == EMPRO_RS_ID: + from ..trigger_states.models import rebuild_trigger_states + rebuild_trigger_states(user) + except ValueError as e: abort(400, str(e)) From a94791837fad06aaad9c155c80ff898109dd010a Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Wed, 13 Mar 2024 16:12:58 -0700 Subject: [PATCH 012/143] correct order of migrations with work done outside branch. log rather than blow up, when a finding missing SDC observations. --- portal/migrations/versions/80c3b1e96c45_.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/portal/migrations/versions/80c3b1e96c45_.py b/portal/migrations/versions/80c3b1e96c45_.py index 3e6ed5d0c..b20cfec5b 100644 --- a/portal/migrations/versions/80c3b1e96c45_.py +++ b/portal/migrations/versions/80c3b1e96c45_.py @@ -1,7 +1,7 @@ """Add sequential hard trigger count to EMPRO trigger_states.triggers domains. Revision ID: 80c3b1e96c45 -Revises: 2e9b9e696bb8 +Revises: 3c871e710277 Create Date: 2023-07-24 17:08:35.128975 """ @@ -9,6 +9,7 @@ from copy import deepcopy from alembic import op from io import StringIO +import logging from sqlalchemy.orm import sessionmaker from portal.database import db from portal.trigger_states.empro_domains import ( @@ -19,10 +20,13 @@ # revision identifiers, used by Alembic. revision = '80c3b1e96c45' -down_revision = '66368e673005' +down_revision = '3c871e710277' Session = sessionmaker() +log = logging.getLogger("alembic.runtime.migration") +log.setLevel(logging.DEBUG) + def upgrade(): # Add sequential counts to appropriate trigger_states rows. @@ -61,9 +65,10 @@ def upgrade(): 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] = {} + # shouldn't happen, SDC typically includes all domains + # but a few records are lacking + log.warning(f"{pid} missing domain {d} in {ts.visit_month} response") + continue if any(v == "hard" for v in improved_triggers["domain"][d].values()): sequential_by_domain[d].append(ts.visit_month) @@ -110,7 +115,8 @@ def downgrade(): improved_triggers = deepcopy(ts.triggers) for d in EMPRO_DOMAINS: if d not in improved_triggers["domain"]: - raise RuntimeError(f"{d} missing from {ts.visit_month} for {pid}") + log.warning(f"{d} missing from {ts.id}(month: {ts.visit_month}) for {pid}") + continue if sequential_hard_trigger_count_key in improved_triggers["domain"][d]: del improved_triggers["domain"][d][sequential_hard_trigger_count_key] output.write(f" removed sequential from {ts.visit_month}:{d} {improved_triggers['domain'][d]}\n") From 419db4b72f46c92ddcc5763d7f54d1d43eb61b76 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Wed, 13 Mar 2024 16:53:30 -0700 Subject: [PATCH 013/143] allow for minor EMPRO consent changes and withdrawal calls w/o raising errors. improve logging when trigger_states become out of sync with qb_timeline due to consent change. --- portal/trigger_states/models.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/portal/trigger_states/models.py b/portal/trigger_states/models.py index 5934a9c79..827cef072 100644 --- a/portal/trigger_states/models.py +++ b/portal/trigger_states/models.py @@ -268,6 +268,7 @@ def rebuild_trigger_states(patient): from ..models.qb_status import patient_research_study_status from ..models.qb_timeline import QBT, update_users_QBT from ..models.research_study import BASE_RS_ID, EMPRO_RS_ID + from ..models.user_consent import consent_withdrawal_dates # Use the timeline data for accurate start dates, etc. update_users_QBT(user_id=patient.id, research_study_id=EMPRO_RS_ID) @@ -296,8 +297,19 @@ def rebuild_trigger_states(patient): # no trigger state data to move, no problem. return + consent_date, wd_date = consent_withdrawal_dates(patient, EMPRO_RS_ID) + month0_dues = [d for d in data if d.visit_month == 0 and d.state == 'due'] + if len(month0_dues) != 1: + raise ValueError(f"{patient.id} failed to find trigger_states due row for month 0") + if (consent_date < month0_dues[0].timestamp) and ( + month0_dues[0].timestamp < consent_date + timedelta(days=30)): + # if the user's month 0 due is within 30 days of consent, don't shift + return + # purge rows and rebuild below # TODO TriggerState.delete and rebuild + current_app.logging.error( + f"{patient.id} trigger_states out of sync with qb_timeline; requires attention!") raise NotImplemented(f"can not adjust trigger_states for {patient.id}") if len([c for c in patient.clinicians]) == 0: From 3cac7bc961324a76d44193aa7eca845011d16438 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Thu, 14 Mar 2024 16:05:21 -0700 Subject: [PATCH 014/143] minor refactor typo; initialize variable only set on a match before testing --- portal/trigger_states/empro_states.py | 1 + 1 file changed, 1 insertion(+) diff --git a/portal/trigger_states/empro_states.py b/portal/trigger_states/empro_states.py index 52fd0066e..19089b956 100644 --- a/portal/trigger_states/empro_states.py +++ b/portal/trigger_states/empro_states.py @@ -95,6 +95,7 @@ def users_trigger_state(user_id, as_of_date=None): if as_of_date is None: as_of_date = datetime.utcnow() + ts = None rows = TriggerState.query.filter( TriggerState.user_id == user_id).order_by( TriggerState.timestamp.desc()) From c7ecaa73a839fbb3f6024c564e8d42de5f7c1de4 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Fri, 15 Mar 2024 07:21:15 -0700 Subject: [PATCH 015/143] alert on production, purge on dev/test, problem trigger_states rows from consent changes. --- portal/migrations/versions/80c3b1e96c45_.py | 36 +++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/portal/migrations/versions/80c3b1e96c45_.py b/portal/migrations/versions/80c3b1e96c45_.py index b20cfec5b..282ac6f07 100644 --- a/portal/migrations/versions/80c3b1e96c45_.py +++ b/portal/migrations/versions/80c3b1e96c45_.py @@ -9,6 +9,7 @@ from copy import deepcopy from alembic import op from io import StringIO +from flask import current_app import logging from sqlalchemy.orm import sessionmaker from portal.database import db @@ -28,6 +29,32 @@ log.setLevel(logging.DEBUG) +def validate_users_trigger_states(session, patient_id): + """Confirm user has sequential visits in trigger states table. + + Due to allowance of moving EMPRO consents and no previous checks, + some users on test have invalid overlapping trigger states rows. + """ + ts_rows = session.query(TriggerState).filter( + TriggerState.user_id == patient_id).order_by(TriggerState.id) + month_counter = -1 + for row in ts_rows: + if row.state == 'due': + # skipping months is okay, but every due should be sequentially greater than previous + if month_counter >= row.visit_month: + raise ValueError(f"{patient_id} expected month > {month_counter}, got {row.visit_month}") + month_counter = row.visit_month + else: + # states other than 'due' should be grouped together with same visit_month + if month_counter != row.visit_month: + raise ValueError(f"{patient_id} expected month {month_counter}, got {row.visit_month}") + +def purge_trigger_states(session, patient_id): + """Clean up test system problems from moving consent dates""" + log.info(f"Purging trigger states for {patient_id}") + session.query(TriggerState).filter(TriggerState.user_id == patient_id).delete() + + def upgrade(): # Add sequential counts to appropriate trigger_states rows. @@ -53,6 +80,15 @@ def upgrade(): # 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. + try: + validate_users_trigger_states(session, pid) + except ValueError as e: + if current_app.config.get('SYSTEM_TYPE') in ('development', 'test'): + purge_trigger_states(session, pid) + continue + else: + raise e + 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) From c562fe8c06a241a023e3d4ead91c52357427ae7f Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Mon, 18 Mar 2024 11:29:14 -0700 Subject: [PATCH 016/143] correct spelling for `testing` SYSTEM_TYPE --- portal/migrations/versions/80c3b1e96c45_.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/portal/migrations/versions/80c3b1e96c45_.py b/portal/migrations/versions/80c3b1e96c45_.py index 282ac6f07..1e21fa515 100644 --- a/portal/migrations/versions/80c3b1e96c45_.py +++ b/portal/migrations/versions/80c3b1e96c45_.py @@ -83,7 +83,7 @@ def upgrade(): try: validate_users_trigger_states(session, pid) except ValueError as e: - if current_app.config.get('SYSTEM_TYPE') in ('development', 'test'): + if current_app.config.get('SYSTEM_TYPE') in ('development', 'testing'): purge_trigger_states(session, pid) continue else: From fea446269a4567854e0665ad8acdb3a3bcb9dd41 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Mon, 18 Mar 2024 13:20:06 -0700 Subject: [PATCH 017/143] typo - TriggerState has `timestamp` column, not `as_of_date` --- portal/trigger_states/empro_states.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/portal/trigger_states/empro_states.py b/portal/trigger_states/empro_states.py index 19089b956..b2eb79619 100644 --- a/portal/trigger_states/empro_states.py +++ b/portal/trigger_states/empro_states.py @@ -154,7 +154,7 @@ def initiate_trigger(user_id, as_of_date=None, rebuilding=False): next_visit = int(ts.visit_month) + 1 current_app.logger.debug(f"transition from {ts} to next due") # generate a new ts, to leave resolved record behind - ts = TriggerState(user_id=user_id, state='unstarted', as_of_date=as_of_date) + ts = TriggerState(user_id=user_id, state='unstarted', timestamp=as_of_date) ts.visit_month = next_visit current_app.logger.debug( "persist-trigger_states-new from initiate_trigger(), " From 7f4bb5ed5f00dd69404133d16a3ab0905e2fda58 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Mon, 18 Mar 2024 17:06:31 -0700 Subject: [PATCH 018/143] incorrect attribute access on previous_triggers.triggers --- portal/trigger_states/empro_domains.py | 4 +++- portal/trigger_states/empro_states.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/portal/trigger_states/empro_domains.py b/portal/trigger_states/empro_domains.py index d5167c4b4..609c099c9 100644 --- a/portal/trigger_states/empro_domains.py +++ b/portal/trigger_states/empro_domains.py @@ -154,7 +154,9 @@ def eval_triggers(self, previous_triggers): for domain in EMPRO_DOMAINS: if domain in self.cur_obs: - prev_triggers_for_domain = previous_triggers["domain"][domain] if previous_triggers else None + prev_triggers_for_domain = ( + previous_triggers["domain"].get(domain) + if previous_triggers else None) dt = DomainTriggers( domain=domain, current_answers=self.cur_obs[domain], diff --git a/portal/trigger_states/empro_states.py b/portal/trigger_states/empro_states.py index b2eb79619..59283f10c 100644 --- a/portal/trigger_states/empro_states.py +++ b/portal/trigger_states/empro_states.py @@ -212,7 +212,7 @@ def evaluate_triggers(qnr): # bring together and evaluate available data for triggers dm = DomainManifold(qnr) previous_triggers = ( - previous if previous and previous.visit_month + 1 == ts.visit_month + previous.triggers if previous and previous.visit_month + 1 == ts.visit_month else None) ts.triggers = dm.eval_triggers(previous_triggers) ts.questionnaire_response_id = qnr.id From 7f2c6eae16195a80b260280383d14a737ccc707b Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Wed, 20 Mar 2024 09:59:06 -0700 Subject: [PATCH 019/143] look for gaps when calling `users_trigger_state` as users will skip months. --- portal/trigger_states/empro_states.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/portal/trigger_states/empro_states.py b/portal/trigger_states/empro_states.py index 59283f10c..3b7ca138a 100644 --- a/portal/trigger_states/empro_states.py +++ b/portal/trigger_states/empro_states.py @@ -81,8 +81,9 @@ class EMPRO_state(StateMachine): def users_trigger_state(user_id, as_of_date=None): """Obtain the latest trigger state for given user - Returns latest TriggerState row for user or creates transient if not - found. + Returns latest TriggerState row for user or creates transient if current + visit_month not found. NB: beyond end of study or withdrawal, the last + valid is returned. :returns TriggerState: with ``state`` attribute meaning: - unstarted: no info avail for user @@ -95,6 +96,7 @@ def users_trigger_state(user_id, as_of_date=None): if as_of_date is None: as_of_date = datetime.utcnow() + vm = lookup_visit_month(user_id, as_of_date) ts = None rows = TriggerState.query.filter( TriggerState.user_id == user_id).order_by( @@ -104,10 +106,16 @@ def users_trigger_state(user_id, as_of_date=None): if as_of_date < ts_row.timestamp: continue ts = ts_row + if ts.visit_month < vm: + current_app.logger.debug( + f"{user_id} trigger state out of sync for visit {vm} (found {ts.visit_month})") + # unset ts given wrong month, to pick up below + ts = None break if not ts: - ts = TriggerState(user_id=user_id, state='unstarted', timestamp=as_of_date) + ts = TriggerState( + user_id=user_id, state='unstarted', timestamp=as_of_date, visit_month=vm) return ts From 89e13ae3e30c3f8fd1e59120ac2d977bb6e1ea1a Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Wed, 20 Mar 2024 09:59:57 -0700 Subject: [PATCH 020/143] API for front-end to set opt-out. incomplete work. --- portal/trigger_states/views.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/portal/trigger_states/views.py b/portal/trigger_states/views.py index 4b54e8ceb..793d7bfea 100644 --- a/portal/trigger_states/views.py +++ b/portal/trigger_states/views.py @@ -57,6 +57,26 @@ def user_triggers(user_id): return jsonify(users_trigger_state(user_id).as_json()) +@trigger_states.route('/api/patient//triggers/opt_out', methods=['POST', 'PUT']) +@crossdomain() +@oauth.require_oauth() +def opt_out(user_id): + """Takes a JSON object defining the domains for which to opt-out + + The ad-hoc JSON expected resembles that returned from `user_triggers()` + simplified to only interpret the domains for which the user chooses to + opt-out. + + :returns TriggerState in JSON for the requested visit month + """ + # confirm view access + get_user(user_id, 'edit', allow_on_url_authenticated_encounters=True) + + # TODO validate visit_month is current, correct user, then persist opt_out data + + return jsonify(users_trigger_state(user_id).as_json()) + + @trigger_states.route('/api/patient//trigger_history') @crossdomain() @oauth.require_oauth() From 1c1b21b8d64df1424b509b231e13879ad10bbf74 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Wed, 20 Mar 2024 14:46:09 -0700 Subject: [PATCH 021/143] creat quick lookup function to determin if a patient has `withdrawn_from_research_study()` --- portal/models/research_study.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/portal/models/research_study.py b/portal/models/research_study.py index bea98b14c..3b56a3752 100644 --- a/portal/models/research_study.py +++ b/portal/models/research_study.py @@ -1,3 +1,4 @@ +from datetime import datetime from sqlalchemy.dialects.postgresql import ENUM from ..cache import TWO_HOURS, cache @@ -122,3 +123,17 @@ def add_static_research_studies(): rs = ResearchStudy.from_fhir(base) if ResearchStudy.query.get(rs.id) is None: db.session.add(rs) + + +def withdrawn_from_research_study(patient_id, research_study_id): + """Check for withdrawn row in patients timeline + + :returns: If withdrawn row found, returns withdrawal date, else None + """ + from .qb_timeline import QBT + from .overall_status import OverallStatus + + withdrawn = QBT.query.filter(QBT.user_id == patient_id).filter( + QBT.research_study_id == research_study_id).filter( + QBT.status == OverallStatus.withdrawn).first() + return withdrawn.at if withdrawn else None From 56f1500472a354c7d4094c757a29a2e41400c34a Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Wed, 20 Mar 2024 14:47:45 -0700 Subject: [PATCH 022/143] consider withdrawal date when looking up user's latest /triggers --- portal/trigger_states/empro_states.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/portal/trigger_states/empro_states.py b/portal/trigger_states/empro_states.py index 3b7ca138a..b20927882 100644 --- a/portal/trigger_states/empro_states.py +++ b/portal/trigger_states/empro_states.py @@ -21,7 +21,7 @@ from ..models.questionnaire_bank import QuestionnaireBank from ..models.questionnaire_response import QuestionnaireResponse from ..models.observation import Observation -from ..models.research_study import EMPRO_RS_ID +from ..models.research_study import EMPRO_RS_ID, withdrawn_from_research_study from ..models.user import User from ..timeout_lock import TimeoutLock @@ -98,6 +98,7 @@ def users_trigger_state(user_id, as_of_date=None): vm = lookup_visit_month(user_id, as_of_date) ts = None + withdrawal_date = withdrawn_from_research_study(user_id, EMPRO_RS_ID) rows = TriggerState.query.filter( TriggerState.user_id == user_id).order_by( TriggerState.timestamp.desc()) @@ -106,7 +107,7 @@ def users_trigger_state(user_id, as_of_date=None): if as_of_date < ts_row.timestamp: continue ts = ts_row - if ts.visit_month < vm: + if ts.visit_month < vm and not withdrawal_date: current_app.logger.debug( f"{user_id} trigger state out of sync for visit {vm} (found {ts.visit_month})") # unset ts given wrong month, to pick up below From b7b07227ee1cb1d7ed8eea4061f96f8c6f0779af Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Wed, 20 Mar 2024 16:22:21 -0700 Subject: [PATCH 023/143] implement and tests for persisting opt-out domains. --- portal/trigger_states/models.py | 43 +++++++++++++++++++++++++++++++++ portal/trigger_states/views.py | 14 ++++++----- tests/fixtures/trigger_state.py | 18 ++++++++++++++ tests/test_trigger_states.py | 10 ++++++++ 4 files changed, 79 insertions(+), 6 deletions(-) diff --git a/portal/trigger_states/models.py b/portal/trigger_states/models.py index 827cef072..3e2c20b71 100644 --- a/portal/trigger_states/models.py +++ b/portal/trigger_states/models.py @@ -1,3 +1,4 @@ +from copy import deepcopy from datetime import datetime, timedelta from flask import current_app from sqlalchemy.dialects.postgresql import ENUM, JSONB @@ -7,6 +8,8 @@ from ..date_tools import FHIR_datetime, weekday_delta from ..models.audit import Audit +opt_out_key = '_opt_out_next_visit' + trigger_state_enum = ENUM( 'unstarted', 'due', @@ -73,6 +76,46 @@ def insert(self, from_copy=False): # see https://github.com/sqlalchemy/sqlalchemy/issues/3640 self = db.session.merge(self) + def apply_opt_out(self, opt_out_dict): + """Given JSON dict with opt_out domains, apply to self.triggers + + The format of the expected JSON can be found in: + tests/fixtures/trigger_state.py::opt_out_submission + + :raise ValueError: if problems found with validating incoming JSON + or self.triggers isn't defined + :returns: modified self + """ + if not self.triggers: + raise ValueError( + f"{self.user_id} has no triggers for {self.visit_month}; " + "can't apply opt_out as requested") + if opt_out_dict.get('user_id') != self.user_id: + raise ValueError(f"user_id({self.user_id} not in opt_out: {opt_out_dict}") + if opt_out_dict.get('visit_month') != self.visit_month: + raise ValueError( + f"user_id({self.user_id} visit_month({self.visit_month}) " + f"not in opt_out: {opt_out_dict}") + + opt_out_of_domains = set() + for d, vals in opt_out_dict['triggers']['domains'].items(): + if vals.get(opt_out_key) is True: + opt_out_of_domains.add(d) + + tc = deepcopy(self.triggers) + for domain, link_triggers in tc['domain'].items(): + if domain in opt_out_of_domains: + link_triggers[opt_out_key] = True + opt_out_of_domains.remove(domain) + + if opt_out_of_domains: + raise ValueError( + f"user_id({self.user_id}):visit_month({self.visit_month}) missing domains " + f"requested in opt_out: {opt_out_of_domains}") + + self.triggers = tc + return self + def hard_trigger_list(self): """Convenience function to return list of hard trigger domains diff --git a/portal/trigger_states/views.py b/portal/trigger_states/views.py index 793d7bfea..322b11911 100644 --- a/portal/trigger_states/views.py +++ b/portal/trigger_states/views.py @@ -1,4 +1,4 @@ -from flask import Blueprint, jsonify, make_response +from flask import Blueprint, abort, jsonify, make_response, request from flask_user import roles_required from .empro_states import extract_observations, users_trigger_state @@ -67,14 +67,16 @@ def opt_out(user_id): simplified to only interpret the domains for which the user chooses to opt-out. - :returns TriggerState in JSON for the requested visit month + :returns: TriggerState in JSON for the requested visit month """ - # confirm view access get_user(user_id, 'edit', allow_on_url_authenticated_encounters=True) + ts = users_trigger_state(user_id) + try: + ts = ts.apply_opt_out(request.json) + except ValueError as e: + abort(400, str(e)) - # TODO validate visit_month is current, correct user, then persist opt_out data - - return jsonify(users_trigger_state(user_id).as_json()) + return jsonify(ts.as_json()) @trigger_states.route('/api/patient//trigger_history') diff --git a/tests/fixtures/trigger_state.py b/tests/fixtures/trigger_state.py index f4887c55b..db09a463a 100644 --- a/tests/fixtures/trigger_state.py +++ b/tests/fixtures/trigger_state.py @@ -52,3 +52,21 @@ def triggered_ts(initialized_patient, mock_triggers): db.session.add(ts) db.session.commit() return db.session.merge(ts) + + +@fixture +def opt_out_submission(): + return { + "user_id": 1, + "visit_month": 0, + "triggers": { + "domains": { + "general_pain": { + "_opt_out_next_visit": True + }, + "fatigue": { + "_opt_out_next_visit": True + } + } + } + } diff --git a/tests/test_trigger_states.py b/tests/test_trigger_states.py index de471e5b4..f190ec46a 100644 --- a/tests/test_trigger_states.py +++ b/tests/test_trigger_states.py @@ -181,6 +181,16 @@ def test_worsening_baseline(): assert dt.triggers[sequential_hard_trigger_count_key] == 1 +def test_apply_opt_out(initialized_patient, processed_ts, opt_out_submission): + from portal.trigger_states.models import opt_out_key + # apply opt out request + user = db.session.merge(initialized_patient) + ts = users_trigger_state(user.id) + result = ts.apply_opt_out(opt_out_submission) + found = [k for k,v in result.triggers['domain'].items() if opt_out_key in v] + assert len(found) == 2 + + def test_ts_trigger_lists(mock_triggers): ts = TriggerState(state='processed', triggers=mock_triggers, user_id=1) assert set(['general_pain', 'joint_pain', 'fatigue']) == set( From f3a9815afa4e579ed5adc78edf07dc8d78e72547 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Wed, 20 Mar 2024 16:30:53 -0700 Subject: [PATCH 024/143] clear any opt_out domains previously set and not included in API call --- portal/trigger_states/models.py | 2 ++ tests/fixtures/trigger_state.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/portal/trigger_states/models.py b/portal/trigger_states/models.py index 3e2c20b71..0fd9b889a 100644 --- a/portal/trigger_states/models.py +++ b/portal/trigger_states/models.py @@ -107,6 +107,8 @@ def apply_opt_out(self, opt_out_dict): if domain in opt_out_of_domains: link_triggers[opt_out_key] = True opt_out_of_domains.remove(domain) + elif opt_out_key in link_triggers: + link_triggers.pop(opt_out_key) if opt_out_of_domains: raise ValueError( diff --git a/tests/fixtures/trigger_state.py b/tests/fixtures/trigger_state.py index db09a463a..a8a51af94 100644 --- a/tests/fixtures/trigger_state.py +++ b/tests/fixtures/trigger_state.py @@ -13,7 +13,7 @@ def mock_triggers(): 'ironman_ss.1': 'soft', 'ironman_ss.2': 'hard'}, 'joint_pain': { 'ironman_ss.4': 'hard', 'ironman_ss.6': 'soft'}, - 'insomnia': {}, + 'insomnia': {'_opt_out_next_visit': True}, 'fatigue': {'ironman_ss.9': 'hard'}, 'anxious': {'ironman_ss.12': 'soft'}, } From 1c96f049bcfd69cfb65ae04fbc89fc88e25f23cd Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Wed, 20 Mar 2024 16:51:23 -0700 Subject: [PATCH 025/143] include problem patient id in timeline error log --- portal/models/qb_timeline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/portal/models/qb_timeline.py b/portal/models/qb_timeline.py index b4fb8de66..2a4d8d6a7 100644 --- a/portal/models/qb_timeline.py +++ b/portal/models/qb_timeline.py @@ -268,7 +268,7 @@ def calc_and_adjust_start(user, research_study_id, qbd, initial_trigger): delta = users_trigger - initial_trigger # this case should no longer be possible; raise the alarm - raise RuntimeError("found initial trigger to differ by: %s", str(delta)) + raise RuntimeError("found user(%d) initial trigger to differ by: %s", user.id, str(delta)) current_app.logger.debug("calc_and_adjust_start delta: %s", str(delta)) return qbd.relative_start + delta From fbca113ebec65ef905620d3747f18faef8287cbf Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Wed, 20 Mar 2024 16:52:00 -0700 Subject: [PATCH 026/143] don't attempt to populate adherence cache on non-patients --- portal/models/reporting.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/portal/models/reporting.py b/portal/models/reporting.py index 6292f383e..2bd142cb0 100644 --- a/portal/models/reporting.py +++ b/portal/models/reporting.py @@ -48,6 +48,11 @@ def single_patient_adherence_data(patient_id, research_study_id): :returns: number of added rows """ + # ignore non-patient requests + patient = User.query.get(patient_id) + if not patient.has_role(ROLE.PATIENT.value): + return + as_of_date = datetime.utcnow() cache_moderation = CacheModeration(key=ADHERENCE_DATA_KEY.format( patient_id=patient_id, @@ -142,7 +147,6 @@ def empro_row_detail(row, ts_reporting): row['content_domains_accessed'] = ', '.join(da) if da else "" added_rows = 0 - patient = User.query.get(patient_id) qb_stats = QB_Status( user=patient, research_study_id=research_study_id, From 1055358fd6e9d9a68a4a98535ade6718abe76cc7 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Wed, 20 Mar 2024 17:08:15 -0700 Subject: [PATCH 027/143] persist opt-out change --- portal/trigger_states/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/portal/trigger_states/views.py b/portal/trigger_states/views.py index 322b11911..82eded62d 100644 --- a/portal/trigger_states/views.py +++ b/portal/trigger_states/views.py @@ -3,6 +3,7 @@ from .empro_states import extract_observations, users_trigger_state from .models import TriggerState +from ..database import db from ..extensions import oauth from ..models.role import ROLE from ..models.user import get_user @@ -76,6 +77,8 @@ def opt_out(user_id): except ValueError as e: abort(400, str(e)) + # persist the change + db.session.commit() return jsonify(ts.as_json()) From 7b167607d1601ab1f024d4fe5aacb93913c47fc2 Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Thu, 21 Mar 2024 09:00:53 -0700 Subject: [PATCH 028/143] EMPRO opt out UI (#4335) https://movember.atlassian.net/browse/TN-3252 - Frontend calls the trigger api, i.e. `/api/patient/[userId]/trigger` to retrieve the latest triggers and check if there is any that has `_sequential_hard_trigger_count` > 1 (meaning then that it has been asked previously) (see example triggers JSON): ``` { ... "timestamp": "2023-07-31T23:17:03+00:00", "triggers": { "domains": { "anxious": { "ironman_ss.11": "hard", "ironman_ss.12": "hard", "ironman_ss.13": "hard", "_sequential_hard_trigger_count": 3 }, "fatigue": { "ironman_ss.10": "hard", "ironman_ss.9": "hard", "_sequential_hard_trigger_count": 2 }, "general_pain": { "ironman_ss.1": "hard", "ironman_ss.2": "hard", "ironman_ss.3": "hard", "_sequential_hard_trigger_count": 3 }, "joint_pain": { "ironman_ss.4": "hard", "ironman_ss.5": "hard", "ironman_ss.6": "hard", "_sequential_hard_trigger_count": 2 }, "sad": { "ironman_ss.17": "hard", "ironman_ss.18": "hard", "ironman_ss.19": "hard", "_sequential_hard_trigger_count": 3 }, } } ... } ``` - If it finds any qualifying domains, it will present the OPT-OUT UI modal to the user, with the checkbox(es) that allow user to select which domain(s) to opt out: ![OptoutInputs](https://github.com/uwcirg/truenth-portal/assets/12942714/e4679692-1f3a-4e75-bed0-27a22376d55f) - frontend submits the domain(s) that user has chosen to opt out to the backend API, `/api/patient//triggers/opt_out`, to be saved on the database for later use (e.g. use in clinician email, adherence report) - One user submits the request, the EMPRO thank you modal that follows will display which(s) domains the user has chosen to opt out: ![OptoutChosen](https://github.com/uwcirg/truenth-portal/assets/12942714/5752b438-eb89-4e4c-a113-98d9a8497a40) --------- Co-authored-by: Amy Chen Co-authored-by: Paul Bugni --- .../templates/eproms/assessment_engine.html | 4 + .../eproms/assessment_engine/ae_base.html | 1 + .../eproms/assessment_engine/ae_due.html | 2 +- .../eproms/assessment_engine/ae_macros.html | 78 +- .../test/SubStudyQuestionnaireTestData.json | 371 ++++++++++ .../data/common/test/TestTriggersData.json | 111 +++ portal/static/js/src/empro.js | 693 +++++++++++++----- portal/static/js/src/modules/TnthAjax.js | 16 + portal/static/less/eproms.less | 130 +++- 9 files changed, 1220 insertions(+), 186 deletions(-) create mode 100644 portal/static/js/src/data/common/test/SubStudyQuestionnaireTestData.json create mode 100644 portal/static/js/src/data/common/test/TestTriggersData.json diff --git a/portal/eproms/templates/eproms/assessment_engine.html b/portal/eproms/templates/eproms/assessment_engine.html index aa86fd852..43aec99d3 100644 --- a/portal/eproms/templates/eproms/assessment_engine.html +++ b/portal/eproms/templates/eproms/assessment_engine.html @@ -31,3 +31,7 @@
{% endif %} +{%- from "eproms/assessment_engine/ae_macros.html" import empro_thankyou_modal, empro_optout_modal -%} +{{empro_optout_modal(user)}} +{{empro_thankyou_modal(user)}} + diff --git a/portal/eproms/templates/eproms/assessment_engine/ae_base.html b/portal/eproms/templates/eproms/assessment_engine/ae_base.html index 58c9df09c..4c2f8f1ac 100644 --- a/portal/eproms/templates/eproms/assessment_engine/ae_base.html +++ b/portal/eproms/templates/eproms/assessment_engine/ae_base.html @@ -4,3 +4,4 @@
{%- block body -%}{%- endblock -%}
+
Loading ...
diff --git a/portal/eproms/templates/eproms/assessment_engine/ae_due.html b/portal/eproms/templates/eproms/assessment_engine/ae_due.html index 1352b93c8..7cf88f4ea 100644 --- a/portal/eproms/templates/eproms/assessment_engine/ae_due.html +++ b/portal/eproms/templates/eproms/assessment_engine/ae_due.html @@ -1,4 +1,4 @@ -{%- from "eproms/assessment_engine/ae_macros.html" import render_header, render_greeting, render_card_content, render_call_to_button, due_card, completed_card, empro_due, empro_completed, empro_expired, completed_cards -%} +{%- from "eproms/assessment_engine/ae_macros.html" import render_header, render_greeting, render_card_content, render_call_to_button, due_card, completed_card, empro_due, empro_completed, empro_expired, completed_cards -%} {% extends "eproms/assessment_engine/ae_base.html" %} {% block head %} diff --git a/portal/eproms/templates/eproms/assessment_engine/ae_macros.html b/portal/eproms/templates/eproms/assessment_engine/ae_macros.html index 48ebd29e4..43e4f678b 100644 --- a/portal/eproms/templates/eproms/assessment_engine/ae_macros.html +++ b/portal/eproms/templates/eproms/assessment_engine/ae_macros.html @@ -210,9 +210,6 @@

{{_("Completed Questionnaires")}}

{% endif %} {%- endcall -%} - {% if substudy_assessment_status.overall_status == OverallStatus.completed %} - {{empro_thankyou_modal(user)}} - {% endif %} {%- endif -%} {%- endmacro -%} {%- macro empro_thankyou_card(full_name="") -%} @@ -227,6 +224,11 @@

{{_("Your Support Team")}}

{%- endmacro -%} +{%- macro empro_no_contact_notice() -%} +
+ Note: You have chosen not to be contacted about . Your care team will not discuss these issues this month. +
+{%- endmacro -%} {%- macro empro_modal_hardTrigger_supportTeam_block(organization="") -%}

{{_("Your Support Team")}}

@@ -234,6 +236,7 @@

{{_("Your Support Team")}}

+ {{empro_no_contact_notice()}}

{{_("To help address any issues, we've informed your care team and they'll be in contact with you soon.")}}

{% if organization %}

{{_("In the meantime, if you have any questions or need assistance, please contact your team at %(organization)s directly. They're happy to help.", organization=organization)}}

@@ -270,8 +273,8 @@

{{_("Your Health Tips")}}

{%- macro empro_thankyou_modal(user) -%} -
{{_("View subject EMPRO report for more information")}}
From da354c1b1488768b85de443f513d292fae140434 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Fri, 29 Mar 2024 07:36:28 -0700 Subject: [PATCH 082/143] discovered need to look for transitional states in testing, as we may catch EMPRO trigger_states rows in the act before achieving the `resolved` state --- portal/migrations/versions/80c3b1e96c45_.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/portal/migrations/versions/80c3b1e96c45_.py b/portal/migrations/versions/80c3b1e96c45_.py index 5553ca900..6591c0874 100644 --- a/portal/migrations/versions/80c3b1e96c45_.py +++ b/portal/migrations/versions/80c3b1e96c45_.py @@ -94,7 +94,7 @@ def upgrade(): sequential_by_domain = defaultdict(list) trigger_states = db.session.query(TriggerState).filter( TriggerState.user_id == pid).filter( - TriggerState.state == "resolved").order_by( + TriggerState.state.in_(("resolved", "triggered", "processed"))).order_by( TriggerState.timestamp.asc()) for ts in trigger_states: improved_triggers = deepcopy(ts.triggers) @@ -145,7 +145,7 @@ def downgrade(): output.write(f"\n\nPatient: {pid}\n") trigger_states = db.session.query(TriggerState).filter( TriggerState.user_id == pid).filter( - TriggerState.state == "resolved").order_by( + TriggerState.state.in_(("resolved", "triggered", "processed"))).order_by( TriggerState.timestamp.asc()) for ts in trigger_states: improved_triggers = deepcopy(ts.triggers) From 92a501f14ee31799135049f373f10b8f9c6d8bdb Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Fri, 29 Mar 2024 07:41:42 -0700 Subject: [PATCH 083/143] pep8 whitespace --- portal/trigger_states/empro_domains.py | 3 ++- portal/trigger_states/models.py | 3 ++- tests/test_trigger_states.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/portal/trigger_states/empro_domains.py b/portal/trigger_states/empro_domains.py index d4a0ee81a..9390f5274 100644 --- a/portal/trigger_states/empro_domains.py +++ b/portal/trigger_states/empro_domains.py @@ -97,7 +97,8 @@ def eval(self): sequential_hard_trigger_count and self.previous_triggers and sequential_hard_trigger_count_key in self.previous_triggers): - sequential_hard_trigger_count = self.previous_triggers[sequential_hard_trigger_count_key] + 1 + sequential_hard_trigger_count = ( + self.previous_triggers[sequential_hard_trigger_count_key] + 1) self._triggers[sequential_hard_trigger_count_key] = sequential_hard_trigger_count diff --git a/portal/trigger_states/models.py b/portal/trigger_states/models.py index bbabb3c33..f32983cf3 100644 --- a/portal/trigger_states/models.py +++ b/portal/trigger_states/models.py @@ -366,7 +366,8 @@ def rebuild_trigger_states(patient): if not tl_query.count(): # User has no timeline data for EMPRO, likely not eligible if TriggerState.query.filter(TriggerState.user_id == patient.id).count(): - current_app.logging.error(f"no EMPRO timeline, yet trigger_states rows for {patient.id}") + current_app.logging.error( + f"no EMPRO timeline, yet trigger_states rows for {patient.id}") return # Capture state in memory for potential reuse when rebuilding diff --git a/tests/test_trigger_states.py b/tests/test_trigger_states.py index 43c74997a..fe5bee7f4 100644 --- a/tests/test_trigger_states.py +++ b/tests/test_trigger_states.py @@ -187,7 +187,7 @@ def test_apply_opt_out(initialized_patient, processed_ts, opt_out_submission): user = db.session.merge(initialized_patient) ts = users_trigger_state(user.id) result = ts.apply_opt_out(opt_out_submission) - found = [k for k,v in result.triggers['domain'].items() if opt_out_this_visit_key in v] + found = [k for k, v in result.triggers['domain'].items() if opt_out_this_visit_key in v] assert len(found) == 2 From ab2226d02c0b3a4f12d694acdbb0343374a898a2 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Fri, 29 Mar 2024 09:06:46 -0700 Subject: [PATCH 084/143] found task to process trigger events taking far too long. no reason to continually look at `resolved` rows. --- portal/trigger_states/empro_states.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/portal/trigger_states/empro_states.py b/portal/trigger_states/empro_states.py index 68bb8fc25..f544cf062 100644 --- a/portal/trigger_states/empro_states.py +++ b/portal/trigger_states/empro_states.py @@ -362,10 +362,6 @@ def process_processed(ts): def process_pending_actions(ts): """Process a trigger states row needing subsequent action""" - # Need to consider state == resolved, as the subsequent - # EMPRO may have become due, but the previous hasn't yet - # received a post intervention QB from staff, noted by - # the action_state: if ( 'action_state' not in ts.triggers or ts.triggers['action_state'] in ( @@ -431,8 +427,7 @@ def process_pending_actions(ts): db.session.commit() # Now seek out any pending actions, such as reminders to staff - for ts in TriggerState.query.filter( - TriggerState.state.in_(('triggered', 'resolved'))): + for ts in TriggerState.query.filter(TriggerState.state == 'triggered'): with TimeoutLock( key=EMPRO_LOCK_KEY.format(user_id=ts.user_id), timeout=NEVER_WAIT): From 63db4c7e79faba7c474eb9b0264c611965e4cc17 Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Fri, 29 Mar 2024 11:07:52 -0700 Subject: [PATCH 085/143] frontend opt out key bugfix, patient report changes --- .../js/src/components/LongitudinalReport.vue | 46 ++++++++++++++----- portal/static/js/src/empro.js | 3 +- portal/static/less/eproms.less | 23 ++++++---- portal/trigger_states/empro_states.py | 19 -------- portal/trigger_states/models.py | 15 ------ 5 files changed, 52 insertions(+), 54 deletions(-) diff --git a/portal/static/js/src/components/LongitudinalReport.vue b/portal/static/js/src/components/LongitudinalReport.vue index 66d4284c0..a768e7c50 100644 --- a/portal/static/js/src/components/LongitudinalReport.vue +++ b/portal/static/js/src/components/LongitudinalReport.vue @@ -4,19 +4,28 @@
- - - - +
+ +
+ + + +
ⓘ (do not contact)
+
+
- < - > - + @@ -39,7 +48,10 @@ import AssessmentReportData from "../data/common/AssessmentReportData.js"; import tnthDates from "../modules/TnthDate.js"; import SYSTEM_IDENTIFIER_ENUM from "../modules/SYSTEM_IDENTIFIER_ENUM"; - import {EMPRO_TRIGGER_PROCCESSED_STATES} from "../data/common/consts.js"; + import { + EMPRO_TRIGGER_STATE_OPTOUT_KEY, + EMPRO_TRIGGER_PROCCESSED_STATES + } from "../data/common/consts.js"; export default { data () { return { @@ -201,6 +213,7 @@ if (!Object.keys(item.triggers.domain[domain]).length) { continue; } + const hasOptOut = !!item.triggers.domain[domain][EMPRO_TRIGGER_STATE_OPTOUT_KEY]; for (let q in item.triggers.domain[domain]) { if (!item.triggers.source || !item.triggers.source.authored) { continue; @@ -211,7 +224,8 @@ if (item.triggers.domain[domain][q] === "hard") { self.triggerData.hardTriggers.push({ "authored": item.triggers.source.authored, - "questionLinkId": q + "questionLinkId": q, + "optOut": hasOptOut }); } /* @@ -226,6 +240,7 @@ } } }); + console.log("trigger data: ", self.triggerData); }, hasTriggers() { return this.hasSoftTriggers() || this.hasHardTriggers(); @@ -236,6 +251,9 @@ hasHardTriggers() { return this.triggerData.hardTriggers.length; }, + hasOptOutTriggers() { + return this.triggerData.hardTriggers.find((item) => item.optOut); + }, hasInProgressData() { return this.assessmentData.filter(item => { return String(item.status).toLowerCase() === "in-progress"; @@ -279,7 +297,7 @@ let hardTriggers = $.grep(this.triggerData.hardTriggers, subitem => { let timeStampComparison = new Date(subitem.authored).toLocaleString() === new Date(authoredDate).toLocaleString(); let linkIdComparison = subitem.questionLinkId === entry.linkId; - return timeStampComparison && linkIdComparison + return !subitem.optOut && timeStampComparison && linkIdComparison }); let softTriggers = $.grep(this.triggerData.softTriggers, subitem => { @@ -287,6 +305,12 @@ let linkIdComparison = subitem.questionLinkId === entry.linkId; return timeStampComparison && linkIdComparison; }); + + let optedOutTriggers = $.grep(this.triggerData.hardTriggers, subitem => { + let timeStampComparison = new Date(subitem.authored).toLocaleString() === new Date(authoredDate).toLocaleString(); + let linkIdComparison = subitem.questionLinkId === entry.linkId; + return subitem.optOut && timeStampComparison && linkIdComparison + }); /* * using valueCoding.code for answer and linkId for question if BOTH question and answer are empty strings @@ -301,7 +325,7 @@ let optionsLength = this.getQuestionOptions(entry.linkId); let answerObj = { q: q, - a: a + (hardTriggers.length?" **": (softTriggers.length?" *": "")), + a: a + (hardTriggers.length?" **": (softTriggers.length?" *": (optedOutTriggers.length? "  ":""))), linkId: entry.linkId, value: answerValue, cssClass: diff --git a/portal/static/js/src/empro.js b/portal/static/js/src/empro.js index c6fd19942..d54229f1d 100644 --- a/portal/static/js/src/empro.js +++ b/portal/static/js/src/empro.js @@ -2,6 +2,7 @@ import EMPRO_DOMAIN_MAPPINGS from "./data/common/empro_domain_mappings.json"; import { EPROMS_SUBSTUDY_ID, EPROMS_SUBSTUDY_QUESTIONNAIRE_IDENTIFIER, + EMPRO_TRIGGER_STATE_OPTOUT_KEY } from "./data/common/consts.js"; import tnthAjax from "./modules/TnthAjax.js"; import tnthDate from "./modules/TnthDate.js"; @@ -113,7 +114,7 @@ emproObj.prototype.setOptoutSubmitData = function () { var triggerObject = {}; EmproObj.selectedOptOutDomains.forEach((item) => { triggerObject[item] = { - _opt_out_next_visit: true, + [EMPRO_TRIGGER_STATE_OPTOUT_KEY]: true, }; }); var submitData = { diff --git a/portal/static/less/eproms.less b/portal/static/less/eproms.less index 5ac7b37e1..cd7b05ff0 100644 --- a/portal/static/less/eproms.less +++ b/portal/static/less/eproms.less @@ -405,6 +405,9 @@ a.btn { padding: 0; margin: 1em 0.2em; } +.sub { + font-size: smaller; +} .chips-list { margin-bottom: 16px; } @@ -3720,7 +3723,7 @@ section.header { } } .nav-arrow { - position: absolute; + //position: absolute; z-index: 5; cursor: pointer; font-size: 14px; @@ -3736,22 +3739,22 @@ section.header { border: 1px solid @mutedColor; &.start { margin-right: 8px; - left: 40%; + //left: 40%; } &.end { padding-left: 12px; - right: 16px; + margin-left: 8px; } &.disabled { opacity: 0.4; cursor: not-allowed; } } - .has-legend { - .nav-arrow { - top: 36px; - } - } + // .has-legend { + // .nav-arrow { + // top: 36px; + // } + // } .report-table { margin-top: 8px; TR:hover { @@ -5443,6 +5446,10 @@ body.vis-on-callback { /* allow page-specific presentation of content after load max-width: 100%; } } +.flex-in-between { + display: flex; + justify-content: space-between; +} .btn-group>.btn:first-child { font-size: @mediumText * 0.89 !important; } diff --git a/portal/trigger_states/empro_states.py b/portal/trigger_states/empro_states.py index e3085dcc4..f544cf062 100644 --- a/portal/trigger_states/empro_states.py +++ b/portal/trigger_states/empro_states.py @@ -213,18 +213,6 @@ def evaluate_triggers(qnr): sm = EMPRO_state(ts) # include previous month resolved row, if available -<<<<<<< HEAD - previous = TriggerState.query.filter( - TriggerState.user_id == qnr.subject_id).filter( - TriggerState.state == 'resolved').order_by( - TriggerState.timestamp.desc()).first() - - # bring together and evaluate available data for triggers - dm = DomainManifold(qnr) - previous_triggers = ( - previous.triggers if previous and previous.visit_month + 1 == ts.visit_month - else None) -======= query = TriggerState.query.filter( TriggerState.user_id == qnr.subject_id).filter( TriggerState.state.in_(('resolved', 'triggered', 'processed'))).filter( @@ -234,7 +222,6 @@ def evaluate_triggers(qnr): # bring together and evaluate available data for triggers dm = DomainManifold(qnr) previous_triggers = previous.triggers if previous else None ->>>>>>> ab2226d02c0b3a4f12d694acdbb0343374a898a2 ts.triggers = dm.eval_triggers(previous_triggers) ts.questionnaire_response_id = qnr.id @@ -245,11 +232,6 @@ def evaluate_triggers(qnr): "persist-trigger_states-new record state change to 'processed' " f"from evaluate_triggers() {ts}") -<<<<<<< HEAD - # a submission closes the window of availability for the - # post-intervention clinician follow up. mark state if - # one is found -======= # a patient submission closes the window of availability for the # post-intervention clinician follow up from any previous visits. # mark state if one is found @@ -257,7 +239,6 @@ def evaluate_triggers(qnr): TriggerState.user_id == qnr.subject_id).filter( TriggerState.state == 'resolved').order_by( TriggerState.timestamp.desc()).first() ->>>>>>> ab2226d02c0b3a4f12d694acdbb0343374a898a2 if previous and previous.triggers.get('action_state') not in ( 'completed', 'missed', 'not applicable', 'withdrawn'): triggers = copy.deepcopy(previous.triggers) diff --git a/portal/trigger_states/models.py b/portal/trigger_states/models.py index 39f433462..f32983cf3 100644 --- a/portal/trigger_states/models.py +++ b/portal/trigger_states/models.py @@ -109,27 +109,16 @@ def apply_opt_out(self, opt_out_dict): opt_out_of_domains = set() for d, vals in opt_out_dict['triggers']['domains'].items(): -<<<<<<< HEAD - if vals.get(opt_out_key) is True: -======= if vals.get(opt_out_this_visit_key) is True: ->>>>>>> ab2226d02c0b3a4f12d694acdbb0343374a898a2 opt_out_of_domains.add(d) tc = deepcopy(self.triggers) for domain, link_triggers in tc['domain'].items(): if domain in opt_out_of_domains: -<<<<<<< HEAD - link_triggers[opt_out_key] = True - opt_out_of_domains.remove(domain) - elif opt_out_key in link_triggers: - link_triggers.pop(opt_out_key) -======= link_triggers[opt_out_this_visit_key] = True opt_out_of_domains.remove(domain) elif opt_out_this_visit_key in link_triggers: link_triggers.pop(opt_out_this_visit_key) ->>>>>>> ab2226d02c0b3a4f12d694acdbb0343374a898a2 if opt_out_of_domains: raise ValueError( @@ -377,12 +366,8 @@ def rebuild_trigger_states(patient): if not tl_query.count(): # User has no timeline data for EMPRO, likely not eligible if TriggerState.query.filter(TriggerState.user_id == patient.id).count(): -<<<<<<< HEAD - current_app.logging.error(f"no EMPRO timeline, yet trigger_states rows for {patient.id}") -======= current_app.logging.error( f"no EMPRO timeline, yet trigger_states rows for {patient.id}") ->>>>>>> ab2226d02c0b3a4f12d694acdbb0343374a898a2 return # Capture state in memory for potential reuse when rebuilding From dd88f400af95bb3682ae3e9be0972d754a063a46 Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Fri, 29 Mar 2024 11:35:05 -0700 Subject: [PATCH 086/143] styling fixes --- .../js/src/components/LongitudinalReport.vue | 6 +++--- portal/static/less/eproms.less | 20 +++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/portal/static/js/src/components/LongitudinalReport.vue b/portal/static/js/src/components/LongitudinalReport.vue index a768e7c50..20f0e085c 100644 --- a/portal/static/js/src/components/LongitudinalReport.vue +++ b/portal/static/js/src/components/LongitudinalReport.vue @@ -10,22 +10,22 @@ -
ⓘ (do not contact)
+ ⓘ (do not contact) + < + >
+
+ + < +
+
+ >
diff --git a/portal/static/less/eproms.less b/portal/static/less/eproms.less index cd7b05ff0..ec5a38c2c 100644 --- a/portal/static/less/eproms.less +++ b/portal/static/less/eproms.less @@ -3721,9 +3721,13 @@ section.header { position: relative; top: 2px; } + .no-contact-legend { + display: inline-block; + margin-left: 8px; + } } .nav-arrow { - //position: absolute; + position: absolute; z-index: 5; cursor: pointer; font-size: 14px; @@ -3739,22 +3743,22 @@ section.header { border: 1px solid @mutedColor; &.start { margin-right: 8px; - //left: 40%; + left: 40%; } &.end { padding-left: 12px; - margin-left: 8px; + right: 16px; } &.disabled { opacity: 0.4; cursor: not-allowed; } } - // .has-legend { - // .nav-arrow { - // top: 36px; - // } - // } + .has-legend { + .nav-arrow { + top: 36px; + } + } .report-table { margin-top: 8px; TR:hover { From 97a2f1492ec09f4d2781dc3c142c33435ecf0a36 Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Fri, 29 Mar 2024 12:01:33 -0700 Subject: [PATCH 087/143] report table heading styling fix --- portal/static/less/eproms.less | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/portal/static/less/eproms.less b/portal/static/less/eproms.less index ec5a38c2c..542bda466 100644 --- a/portal/static/less/eproms.less +++ b/portal/static/less/eproms.less @@ -3765,6 +3765,14 @@ section.header { background-color: lighten(@muterColor, 5%); } } + @media (min-width: 1400px) { + .report-table { + TH:last-of-type { + min-width: 200px; + padding-right: 64px; + } + } + } .container { padding-right: 0; padding-left: 0; From f9717ce9a560da3fddc7faee7303e37c19bbcf71 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Tue, 2 Apr 2024 14:31:45 -0700 Subject: [PATCH 088/143] In event of a LockTimeout, when processing `fire_trigger_events()` on user opt-out input, catch and squelch the noise. It'll get picked up on next scheduled run. --- portal/trigger_states/views.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/portal/trigger_states/views.py b/portal/trigger_states/views.py index 3ba825db1..845064642 100644 --- a/portal/trigger_states/views.py +++ b/portal/trigger_states/views.py @@ -7,6 +7,7 @@ from ..extensions import oauth from ..models.role import ROLE from ..models.user import get_user +from ..timeout_lock import LockTimeout from ..views.crossdomain import crossdomain trigger_states = Blueprint('trigger_states', __name__) @@ -82,7 +83,12 @@ def opt_out(user_id): if ts.opted_out_domains(): # if user opted out of at least one, process immediately - fire_trigger_events() + try: + fire_trigger_events() + except LockTimeout: + # deadlock of sorts - skip out expecting the scheduled + # job to pick up the state next run + pass return jsonify(ts.as_json()) From 6d02a38f344d8545f8186b3824221c9a478235c3 Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Tue, 2 Apr 2024 16:39:55 -0700 Subject: [PATCH 089/143] Post Tx Report display fixes, patient report styling fixes, add retry attempt for triggers API call --- .../eproms/assessment_engine/ae_base.html | 2 +- .../js/src/components/LongitudinalReport.vue | 74 ++++++++++--------- portal/static/js/src/data/common/consts.js | 1 + portal/static/js/src/empro.js | 13 ++-- portal/static/js/src/modules/TnthAjax.js | 67 ++++++++++------- portal/static/js/src/profile.js | 2 + portal/templates/profile/profile_macros.html | 4 +- 7 files changed, 96 insertions(+), 67 deletions(-) diff --git a/portal/eproms/templates/eproms/assessment_engine/ae_base.html b/portal/eproms/templates/eproms/assessment_engine/ae_base.html index 4c2f8f1ac..5d2bd9b62 100644 --- a/portal/eproms/templates/eproms/assessment_engine/ae_base.html +++ b/portal/eproms/templates/eproms/assessment_engine/ae_base.html @@ -4,4 +4,4 @@
{%- block body -%}{%- endblock -%}
-
Loading ...
+
{{_("Loading")}} ...
diff --git a/portal/static/js/src/components/LongitudinalReport.vue b/portal/static/js/src/components/LongitudinalReport.vue index 20f0e085c..14e4b4c16 100644 --- a/portal/static/js/src/components/LongitudinalReport.vue +++ b/portal/static/js/src/components/LongitudinalReport.vue @@ -6,7 +6,7 @@
-
+
@@ -14,33 +14,35 @@
- < - > -
- <
- >
- - - - - - - - - - - -
-
- -
-
- - -
- {{item.code[0].display}} - {{item.text}} - - -
+
+ < + > + + + + + + + + + + + + +
+
+ +
+
+ + +
+ {{item.code[0].display}} + {{item.text}} + + +
+
@@ -213,7 +215,7 @@ if (!Object.keys(item.triggers.domain[domain]).length) { continue; } - const hasOptOut = !!item.triggers.domain[domain][EMPRO_TRIGGER_STATE_OPTOUT_KEY]; + const hasOptOut = item.triggers.domain[domain][EMPRO_TRIGGER_STATE_OPTOUT_KEY]; for (let q in item.triggers.domain[domain]) { if (!item.triggers.source || !item.triggers.source.authored) { continue; @@ -329,11 +331,15 @@ linkId: entry.linkId, value: answerValue, cssClass: - //last - answerValue >= optionsLength.length ? "darkest" : - //penultimate - (answerValue >= optionsLength.length - 1 ? "darker": - (answerValue <= 1 ? "no-value": "")) + optedOutTriggers.length ? + "warning" : + //last + ( + answerValue >= optionsLength.length ? "darkest" : + //penultimate + (answerValue >= optionsLength.length - 1 ? "darker": + (answerValue <= 1 ? "no-value": "")) + ) }; this.data[index].data.push(answerObj); let currentDomain = ""; diff --git a/portal/static/js/src/data/common/consts.js b/portal/static/js/src/data/common/consts.js index 0a49e3f42..f12a690c6 100644 --- a/portal/static/js/src/data/common/consts.js +++ b/portal/static/js/src/data/common/consts.js @@ -14,6 +14,7 @@ export var EMPRO_TRIGGER_UNPROCCESSED_STATES = [ ]; export var EMPRO_TRIGGER_STATE_OPTOUT_KEY = "_opt_out_this_visit"; export var EMPRO_TRIGGER_IN_PROCESS_STATE = "inprocess"; // see /trigger_states/empro_states.py for explanation of different trigger states +export var EMPRO_TRIGGER_WITHDRAWN_STATE = "withdrawn"; // patient withdrawn export var EPROMS_SUBSTUDY_QUESTIONNAIRE_IDENTIFIER = "ironman_ss"; export var EMPRO_POST_TX_QUESTIONNAIRE_IDENTIFIER = "ironman_ss_post_tx"; //pre-existing translated text diff --git a/portal/static/js/src/empro.js b/portal/static/js/src/empro.js index d54229f1d..24c3c79ec 100644 --- a/portal/static/js/src/empro.js +++ b/portal/static/js/src/empro.js @@ -299,6 +299,12 @@ emproObj.prototype.initOptOutModal = function (autoShow) { } $("#emproOptOutModal").modal(autoShow ? "show" : "hide"); }; +emproObj.prototype.onDetectOptOutDomains = function() { + this.populateOptoutInputItems(); + this.initOptOutElementEvents(); + this.initOptOutModal(true); + this.initThankyouModal(false); +}; emproObj.prototype.initReportLink = function () { if (!this.hasThankyouModal()) return; @@ -417,7 +423,7 @@ emproObj.prototype.init = function () { this.initTriggerDomains( { - maxTryAttempts: isDebugging && !autoShowModal ? 1 : 5, + maxTryAttempts: !autoShowModal ? 1 : 5, clearCache: autoShowModal, }, (result) => { @@ -436,10 +442,7 @@ emproObj.prototype.init = function () { localStorage.setItem(this.cachedAccessKey, `true`); // console.log("Opt out domain? ", this.optOutDomains); if (this.optOutDomains.length > 0) { - this.populateOptoutInputItems(); - this.initOptOutElementEvents(); - this.initOptOutModal(true); - this.initThankyouModal(false); + this.onDetectOptOutDomains(); return; } } diff --git a/portal/static/js/src/modules/TnthAjax.js b/portal/static/js/src/modules/TnthAjax.js index 26672948a..545357c90 100644 --- a/portal/static/js/src/modules/TnthAjax.js +++ b/portal/static/js/src/modules/TnthAjax.js @@ -32,6 +32,7 @@ export default { /*global $ */ var fieldHelper = this.FieldLoaderHelper, targetField = params.targetField || null; callback = callback || function() {}; params.attempts++; + params.checkAuth = ["post", "put", "delete"].indexOf(String(method).toLowerCase()) !== -1; fieldHelper.showLoader(targetField); if (params.useWorker && window.Worker && !Utility.isTouchDevice()) { /*global isTouchDevice()*/ Utility.initWorker(url, params, function(result) { /*global initWorker*/ @@ -63,31 +64,45 @@ export default { /*global $ */ "pragma": "no-cache" }; } - $.ajax(params).done(function(data) { - params.attempts = 0; - if (data) { - fieldHelper.showUpdate(targetField); - callback(data); - } else { - fieldHelper.showError(targetField); - callback({"error": DEFAULT_SERVER_DATA_ERROR, "data": false}); - } - }).fail(function(xhr) { - if (params.attempts < params.max_attempts) { - (function(self, url, method, userId, params, callback) { - setTimeout(function () { - self.sendRequest(url, method, userId, params, callback); - }, REQUEST_TIMEOUT_INTERVAL); //retry after 5 seconds - })(self, url, method, userId, params, callback); - } else { - fieldHelper.showError(targetField); - callback({"error": DEFAULT_SERVER_DATA_ERROR, "data": xhr}); - self.sendError(xhr, url, userId, params); - //reset attempts after reporting error so we know how many attempts have been made - //multiple attempts can signify server not being responsive or busy network - params.attempts = 0; - } - }); + var ajaxCall = () => $.ajax(params).done(function(data) { + params.attempts = 0; + if (data) { + fieldHelper.showUpdate(targetField); + callback(data); + } else { + fieldHelper.showError(targetField); + callback({"error": DEFAULT_SERVER_DATA_ERROR, "data": false}); + } + }).fail(function(xhr) { + if (params.attempts < params.max_attempts) { + (function(self, url, method, userId, params, callback) { + setTimeout(function () { + self.sendRequest(url, method, userId, params, callback); + }, REQUEST_TIMEOUT_INTERVAL); //retry after 5 seconds + })(self, url, method, userId, params, callback); + } else { + fieldHelper.showError(targetField); + callback({"error": DEFAULT_SERVER_DATA_ERROR, "data": xhr}); + self.sendError(xhr, url, userId, params); + //reset attempts after reporting error so we know how many attempts have been made + //multiple attempts can signify server not being responsive or busy network + params.attempts = 0; + } + }); + if (params.checkAuth && params.attempts <= 1) { + $.ajax("/api/me").done( + function() { + console.log("user authorized"); + ajaxCall(); + } + ).fail(function() { + if (callback) { + callback({"error": DEFAULT_SERVER_DATA_ERROR}); + } + }); + return; + } + ajaxCall(); }, "sendError": function(xhr, url, userId, params) { if (!xhr) { return false; } @@ -356,7 +371,7 @@ export default { /*global $ */ params.retryAttempt++; setTimeout(function() { this.getSubStudyTriggers(userId, params, callback); - }.bind(this), 1000*params.retryAttempt); + }.bind(this), 1500*params.retryAttempt); return false; } params.retryAttempt = 0; diff --git a/portal/static/js/src/profile.js b/portal/static/js/src/profile.js index 0faf62928..61fc5d526 100644 --- a/portal/static/js/src/profile.js +++ b/portal/static/js/src/profile.js @@ -15,6 +15,7 @@ import { EMPRO_POST_TX_QUESTIONNAIRE_IDENTIFIER, EMPRO_TRIGGER_STATE_OPTOUT_KEY, EMPRO_TRIGGER_UNPROCCESSED_STATES, + EMPRO_TRIGGER_WITHDRAWN_STATE, REQUIRED_PI_ROLES, REQUIRED_PI_ROLES_WARNING_MESSAGE, } from "./data/common/consts.js"; @@ -1594,6 +1595,7 @@ export default (function() { if (!this.subStudyTriggers.data) { return true; } + if (this.getPostTxActionStatus() === EMPRO_TRIGGER_WITHDRAWN_STATE) return true; // subject withdrawn return this.getPostTxActionStatus() === "completed" || this.subStudyTriggers.data.resolution; }, onResponseChangeFieldEvent: function(event) { diff --git a/portal/templates/profile/profile_macros.html b/portal/templates/profile/profile_macros.html index 6a13dd055..01aea3a90 100644 --- a/portal/templates/profile/profile_macros.html +++ b/portal/templates/profile/profile_macros.html @@ -895,7 +895,9 @@

{% if person and person.username %}{{ _("for") + " " + pe
{{_("Actions Required (for Clinicians/Site Staff)")}}
{{_("EMPRO questionnaire completed on:")}}
-
{{_("ACTION REQUIRED for high distress areas:")}} +
+

{{_("ACTION REQUIRED for high distress areas:")}}

+

{{_("High distress areas")}}

From feb0f7476e39b7c5f02f0c7226ae7b79ba131559 Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Tue, 2 Apr 2024 16:42:24 -0700 Subject: [PATCH 090/143] add missing file --- portal/static/less/eproms.less | 40 +++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/portal/static/less/eproms.less b/portal/static/less/eproms.less index 542bda466..fc2abb623 100644 --- a/portal/static/less/eproms.less +++ b/portal/static/less/eproms.less @@ -2283,7 +2283,7 @@ div.footer-wrapper.right-panel { } } #longitudinalReportContainer { - margin-top: 24px; + margin-top: 16px; } #timeWarpContainer { cursor: pointer; @@ -2293,6 +2293,9 @@ div.footer-wrapper.right-panel { .prompt .edit-icon { margin-right: 8px; } + .text { + line-height: 2; + } .text:hover { color: darken(@mutedColor, 10%); } @@ -3695,6 +3698,12 @@ section.header { .title { font-weight: 500; } + .legend-list { + display: flex; + flex-direction: row; + gap: 2px; + justify-content: flex-start; + } .soft-trigger-legend, .hard-trigger-legend, .in-progress-legend { @@ -3724,6 +3733,7 @@ section.header { .no-contact-legend { display: inline-block; margin-left: 8px; + color: @warningColor; } } .nav-arrow { @@ -3741,6 +3751,7 @@ section.header { padding-top: 4px; color: @mutedColor; border: 1px solid @mutedColor; + z-index: 100; &.start { margin-right: 8px; left: 40%; @@ -3754,10 +3765,8 @@ section.header { cursor: not-allowed; } } - .has-legend { - .nav-arrow { - top: 36px; - } + .table-container { + position: relative; } .report-table { margin-top: 8px; @@ -3783,6 +3792,9 @@ section.header { color: @mutedColor; padding: 16px 16px 16px 24px; letter-spacing: 0.1rem; + position: sticky; + top: 0; + z-index: 10; } TH.title { text-transform: uppercase; @@ -3799,6 +3811,10 @@ section.header { padding: 8px 16px; min-width: 132px; text-align: center; + &.warning { + background-color: darken(@warningColor, 1%); + color: #FFF + } &.darker { background: lighten(@mutedColor, 5%); color: #FFF; @@ -4084,6 +4100,7 @@ section.header { } p { margin: 0 0 12px; + line-height: 1.535; } .item { .ck-input { @@ -4297,6 +4314,9 @@ section.header { .item { display: flex; gap: 8px; + input:hover { + cursor: pointer; + } &:not(:last-of-type) { margin-bottom:8px; } @@ -4308,6 +4328,11 @@ section.header { } .modal-footer { text-align: center; + .btn { + font-size: 0.9em; + padding: 1em; + min-width: 112px; + } .btn-empro-primary { background-color: @emproBtnPrimaryColor; color: #FFF; @@ -5115,6 +5140,11 @@ input.clinic { th, td { padding: 8px; } + th { + position: sticky; + top: 0; + background-color: #FFF; + } } #userSessionReportDetailHeader a[href] { From 5c30bb99cc4c7155881c59fc55aa3e178d147eaf Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Tue, 2 Apr 2024 16:48:25 -0700 Subject: [PATCH 091/143] scale back critical section, with more frequent runs and opt-out user actions conflicting more with wide scope designed for task defense. commit includes excessive logging to locate false assumption with not delaying for pending user opt-out feedback --- portal/trigger_states/empro_states.py | 43 ++++++++++++++++----------- portal/trigger_states/models.py | 2 ++ 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/portal/trigger_states/empro_states.py b/portal/trigger_states/empro_states.py index f544cf062..7a8ea451d 100644 --- a/portal/trigger_states/empro_states.py +++ b/portal/trigger_states/empro_states.py @@ -23,7 +23,7 @@ from ..models.observation import Observation from ..models.research_study import EMPRO_RS_ID, withdrawn_from_research_study from ..models.user import User -from ..timeout_lock import TimeoutLock +from ..timeout_lock import TimeoutLock, LockTimeout EMPRO_LOCK_KEY = "empro-trigger-state-lock-{user_id}" OPT_OUT_DELAY = 300 # seconds to allow user to provide opt-out choices @@ -279,10 +279,12 @@ def delay_processing(ts): """Give user time to respond to opt-out prompt if applicable""" if not ts.sequential_threshold_reached(): # not applicable unless at least one domain has adequate count + current_app.logger.debug("QQQ sequential_threshold_reached false, bail") return if ts.opted_out_domains(): # user must have already replied, if opted out of at least one + current_app.logger.debug("QQQ user already opted out") return # check time since row transitioned to current state. delay @@ -405,34 +407,41 @@ def process_pending_actions(ts): current_app.logger.debug( f"persist-trigger_states-change reminder_due clause {ts}") - # Main loop for this task function. Two locks employed to avoid race - # conditions with other threads updating trigger_states rows. + # Main loop for this task function. # # As this is a recurring, scheduled job, simply skip over any locked keys # to pick up next run. # - # 1. single task concurrency (key='fire_trigger_events') - # 2. per user lock also checked elsewhere (key=EMPRO_LOCK_KEY(user_id)) + # The per-user lock also is checked elsewhere (key=EMPRO_LOCK_KEY(user_id)), + # to prevent concurrent state transitions on a given user. NEVER_WAIT = 0 - with TimeoutLock(key='fire_trigger_events', timeout=NEVER_WAIT): - # seek out any pending "processed" work, i.e. triggers recently - # evaluated - for ts in TriggerState.query.filter(TriggerState.state == 'processed'): - if delay_processing(ts): - continue + + # seek out any pending "processed" work, i.e. triggers recently + # evaluated + for ts in TriggerState.query.filter(TriggerState.state == 'processed'): + if delay_processing(ts): + continue + try: with TimeoutLock( key=EMPRO_LOCK_KEY.format(user_id=ts.user_id), timeout=NEVER_WAIT): process_processed(ts) - db.session.commit() + db.session.commit() + except LockTimeout: + # will get picked up next scheduled run - ignore + pass # Now seek out any pending actions, such as reminders to staff for ts in TriggerState.query.filter(TriggerState.state == 'triggered'): - with TimeoutLock( - key=EMPRO_LOCK_KEY.format(user_id=ts.user_id), - timeout=NEVER_WAIT): - process_pending_actions(ts) - db.session.commit() + try: + with TimeoutLock( + key=EMPRO_LOCK_KEY.format(user_id=ts.user_id), + timeout=NEVER_WAIT): + process_pending_actions(ts) + db.session.commit() + except LockTimeout: + # will get picked up next scheduled run - ignore + pass def empro_staff_qbd_accessor(qnr): diff --git a/portal/trigger_states/models.py b/portal/trigger_states/models.py index f32983cf3..fe1ef9b27 100644 --- a/portal/trigger_states/models.py +++ b/portal/trigger_states/models.py @@ -172,11 +172,13 @@ def sequential_threshold_reached(self): """ from .empro_domains import sequential_hard_trigger_count_key if not self.triggers: + current_app.logger.debug("QQQ no triggers in sequential_threshold_reached") return for domain, link_triggers in self.triggers['domain'].items(): if link_triggers.get(sequential_hard_trigger_count_key, 0) > 2: return True + current_app.logger.debug(f"QQQ sequential not found in {link_triggers} sequential_threshold_reached") def reminder_due(self, as_of_date=None): """Determine if reminder is due from internal state""" From f08027098c08c88032fd54cd6a65e8aad9e8dda6 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Tue, 2 Apr 2024 17:25:03 -0700 Subject: [PATCH 092/143] more debug logging - no content change --- portal/trigger_states/empro_states.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/portal/trigger_states/empro_states.py b/portal/trigger_states/empro_states.py index 7a8ea451d..778a1ac11 100644 --- a/portal/trigger_states/empro_states.py +++ b/portal/trigger_states/empro_states.py @@ -276,6 +276,7 @@ def fire_trigger_events(): now = datetime.utcnow() def delay_processing(ts): + current_app.logger.debug("QQQ enter sequential_threshold_reached") """Give user time to respond to opt-out prompt if applicable""" if not ts.sequential_threshold_reached(): # not applicable unless at least one domain has adequate count @@ -289,6 +290,7 @@ def delay_processing(ts): # check time since row transitioned to current state. delay # till threshold reached + current_app.logger.debug(f"QQQ {ts.timestamp + timedelta(seconds=OPT_OUT_DELAY)} {datetime.utcnow()}") if ts.timestamp + timedelta(seconds=OPT_OUT_DELAY) < datetime.utcnow(): return True @@ -419,8 +421,10 @@ def process_pending_actions(ts): # seek out any pending "processed" work, i.e. triggers recently # evaluated for ts in TriggerState.query.filter(TriggerState.state == 'processed'): + current_app.logger.debug("QQQ call delay_processing") if delay_processing(ts): continue + current_app.logger.debug("QQQ delay_processing didn't work") try: with TimeoutLock( key=EMPRO_LOCK_KEY.format(user_id=ts.user_id), From 5558e81a5cb8d08fcefe88b253b45b238a05a165 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Tue, 2 Apr 2024 17:59:47 -0700 Subject: [PATCH 093/143] corrected logic error in time comparison --- portal/trigger_states/empro_states.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/portal/trigger_states/empro_states.py b/portal/trigger_states/empro_states.py index 778a1ac11..823cfcc84 100644 --- a/portal/trigger_states/empro_states.py +++ b/portal/trigger_states/empro_states.py @@ -291,7 +291,8 @@ def delay_processing(ts): # check time since row transitioned to current state. delay # till threshold reached current_app.logger.debug(f"QQQ {ts.timestamp + timedelta(seconds=OPT_OUT_DELAY)} {datetime.utcnow()}") - if ts.timestamp + timedelta(seconds=OPT_OUT_DELAY) < datetime.utcnow(): + if ts.timestamp < timedelta(seconds=OPT_OUT_DELAY) + datetime.utcnow(): + current_app.logger.debug(f"QQQ return True from delay_processing") return True def send_n_report(em, context, record): From e7d755702cbe47f792296af64ec8276c63cd7aa2 Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Fri, 5 Apr 2024 15:16:15 -0700 Subject: [PATCH 094/143] fix retry ateempt number, fix text display --- portal/static/js/src/empro.js | 2 +- portal/templates/profile/profile_macros.html | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/portal/static/js/src/empro.js b/portal/static/js/src/empro.js index 24c3c79ec..ece64fc1d 100644 --- a/portal/static/js/src/empro.js +++ b/portal/static/js/src/empro.js @@ -216,7 +216,7 @@ emproObj.prototype.handleSubmitOptoutData = function () { EmproObj.userId, { data: JSON.stringify(EmproObj.optOutSubmitData), - max_attempts: 2, + max_attempts: 3, }, (data) => { return EmproObj.onAfterSubmitOptoutData(data); diff --git a/portal/templates/profile/profile_macros.html b/portal/templates/profile/profile_macros.html index 01aea3a90..cbe8a9ee4 100644 --- a/portal/templates/profile/profile_macros.html +++ b/portal/templates/profile/profile_macros.html @@ -873,12 +873,9 @@

{% if person and person.username %}{{ _("for") + " " + pe {%- endmacro -%} {% macro postInterventionOptoutDomains() %}
-

- {{_("NOTE: The participant has chosen not to be contacted about some issues.")}} -
- {{_("High distressed areas ( Do not contact ): ")}} - -

+

{{_("NOTE: The participant has chosen not to be contacted about some issues.")}}

+

{{_("NOTE: The participant has chosen not to be contacted.")}}

+

{{_("High distressed areas ( Do not contact ): ")}}

{% endmacro %} {%- macro postInterventionQuestionnaire(person) -%} From e42704554e6356515cf1c69d4713052137561320 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Mon, 8 Apr 2024 09:06:35 -0700 Subject: [PATCH 095/143] plug in LR content UUIDs for opted-out clinician emails --- portal/config/eproms/AppText.json | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/portal/config/eproms/AppText.json b/portal/config/eproms/AppText.json index 1d2a50b3e..f13910021 100644 --- a/portal/config/eproms/AppText.json +++ b/portal/config/eproms/AppText.json @@ -191,7 +191,7 @@ "resourceType": "AppText" }, { - "custom_text": "{config[LR_ORIGIN]}/c/portal/truenth/asset/mail?version=latest&uuid=faab8398-fa93-f1ca-9d81-3ddc38769d96", + "custom_text": "{config[LR_ORIGIN]}/c/portal/truenth/asset/mail?version=latest&uuid=73361f9b-f79a-ddaa-7437-c304af8953ef", "name": "empro clinician trigger notification all opted out", "resourceType": "AppText" }, @@ -200,11 +200,6 @@ "name": "empro clinician trigger reminder", "resourceType": "AppText" }, - { - "custom_text": "{config[LR_ORIGIN]}/c/portal/truenth/asset/mail?version=latest&uuid=86b1c0ef-e3ee-e264-0190-71678d21b6a5", - "name": "empro clinician trigger reminder all opted out", - "resourceType": "AppText" - }, { "custom_text": "

(greeting),

This email was sent to you because you are a patient at (clinic name) and consented to participate in the Prostate Cancer Outcomes - (parent org) Registry Study.

This is an invitation to use the TrueNTH website, where you will report on your health. Your participation will help us collectively improve the care that men receive during their prostate cancer journey.

To complete your first questionnaire, please first verify your account.

You can also access the TrueNTH website with this link:

{0}

Save this email so that you can return to TrueNTH any time.

If you have any queries, please contact your representative at (clinic name).

", "name": "profileSendEmail invite email_body", From ef81016566ad22b30705906751742ffa39215c26 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Mon, 8 Apr 2024 11:49:42 -0700 Subject: [PATCH 096/143] new config var OPT_OUT_DISABLED_ORG_IDS , potentially empty list of organizations not wanting the EMPRO opt-out feature. --- portal/config/config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/portal/config/config.py b/portal/config/config.py index d3fecfc07..3b1ccbf91 100644 --- a/portal/config/config.py +++ b/portal/config/config.py @@ -117,6 +117,8 @@ class BaseConfig(object): TOKEN_LIFE_IN_DAYS = 30 # Used for emailed URL tokens MULTIPROCESS_LOCK_TIMEOUT = 30 # Lock on QB timeline generation + OPT_OUT_DISABLED_ORG_IDS = os.environ.get('OPT_OUT_DISABLED_ORG_IDS', []) + # Medidata integration configuration # disable creation and editing of patients when active PROTECTED_ORG = os.environ.get('PROTECTED_ORG') # use organization name From 0e2ed988ec9e1386c51c45065756c7ad7471e41c Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Mon, 8 Apr 2024 12:47:42 -0700 Subject: [PATCH 097/143] OPT_OUT_DISABLED_ORG_IDS set to UK EMPRO org ids --- portal/config/eproms/site_persistence_file.json | 1 + 1 file changed, 1 insertion(+) diff --git a/portal/config/eproms/site_persistence_file.json b/portal/config/eproms/site_persistence_file.json index 1b71aff1f..d5849a838 100644 --- a/portal/config/eproms/site_persistence_file.json +++ b/portal/config/eproms/site_persistence_file.json @@ -20,6 +20,7 @@ "CONSENT_EDIT_PERMISSIBLE_ROLES = ['staff', 'staff_admin', 'admin']\n", "CUSTOM_PATIENT_DETAIL = True\n", "LOCALIZED_AFFILIATE_ORG = 'TrueNTH Global Registry'\n", + "OPT_OUT_DISABLED_ORG_IDS = [14620, 14656]\n", "ORGS_W_CUSTOM_INVITES = ['TrueNTH Global Registry', 'IRONMAN']\n", "SHOW_WELCOME = False\n", "HIDE_TRUENTH_ID_FIELD = True\n", From 072cfc24d3a702d6c0a5e215766dc9b5c5440f7c Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Tue, 9 Apr 2024 11:03:54 -0700 Subject: [PATCH 098/143] Opt out feature - add frontend check for whether opt out is disabled for user org (#4375) Add frontend check to determine whether user's org is in the list of orgs that do not allow OPT OUT feature yet, i.e. via config variable, `OPT_OUT_DISABLED_ORG_IDS` --------- Co-authored-by: Amy Chen --- portal/static/js/src/empro.js | 100 +++++++++++++------ portal/static/js/src/mixins/CurrentUser.js | 3 + portal/static/js/src/profile.js | 3 +- portal/templates/profile/profile_macros.html | 4 +- portal/views/portal.py | 1 + 5 files changed, 75 insertions(+), 36 deletions(-) diff --git a/portal/static/js/src/empro.js b/portal/static/js/src/empro.js index ece64fc1d..cb9f3b0bc 100644 --- a/portal/static/js/src/empro.js +++ b/portal/static/js/src/empro.js @@ -2,10 +2,11 @@ import EMPRO_DOMAIN_MAPPINGS from "./data/common/empro_domain_mappings.json"; import { EPROMS_SUBSTUDY_ID, EPROMS_SUBSTUDY_QUESTIONNAIRE_IDENTIFIER, - EMPRO_TRIGGER_STATE_OPTOUT_KEY + EMPRO_TRIGGER_STATE_OPTOUT_KEY, } from "./data/common/consts.js"; import tnthAjax from "./modules/TnthAjax.js"; import tnthDate from "./modules/TnthDate.js"; +import { CurrentUserObj } from "./mixins/CurrentUser.js"; import TestResponsesJson from "./data/common/test/SubStudyQuestionnaireTestData.json"; import TestTriggersJson from "./data/common/test/TestTriggersData.json"; import { getUrlParameter } from "./modules/Utility"; @@ -22,9 +23,11 @@ var emproObj = function () { this.hasHardTrigger = false; this.hasSoftTrigger = false; this.userId = 0; + this.userOrgs = []; this.visitMonth = 0; this.authorDate = null; this.cachedAccessKey = null; + this.optOutNotAllowed = false; }; emproObj.prototype.getDomainDisplay = function (domain) { if (!domain) return ""; @@ -114,7 +117,7 @@ emproObj.prototype.setOptoutSubmitData = function () { var triggerObject = {}; EmproObj.selectedOptOutDomains.forEach((item) => { triggerObject[item] = { - [EMPRO_TRIGGER_STATE_OPTOUT_KEY]: true, + [EMPRO_TRIGGER_STATE_OPTOUT_KEY]: true, }; }); var submitData = { @@ -299,7 +302,7 @@ emproObj.prototype.initOptOutModal = function (autoShow) { } $("#emproOptOutModal").modal(autoShow ? "show" : "hide"); }; -emproObj.prototype.onDetectOptOutDomains = function() { +emproObj.prototype.onDetectOptOutDomains = function () { this.populateOptoutInputItems(); this.initOptOutElementEvents(); this.initOptOutModal(true); @@ -327,15 +330,42 @@ emproObj.prototype.initTriggerItemsVis = function () { return; } }; +emproObj.prototype.checkUserOrgAllowOptOut = function (userId, userOrgs, callback) { + callback = callback || function () {}; + if (!userId || !userOrgs || !userOrgs.length) { + callback(false); + return; + } + const OPT_OUT_ORGS_KEY = "OPT_OUT_DISABLED_ORG_IDS"; + // get opt out disabled orgs from setting + tnthAjax.setting(OPT_OUT_ORGS_KEY, userId, null, (data) => { + if (!data || !data[OPT_OUT_ORGS_KEY]) { + callback(false); + return; + } + const optOutDisabledOrgs = data[OPT_OUT_ORGS_KEY]; + if (!optOutDisabledOrgs.length) { + callback(false); + return; + } + const orgsToCompare = optOutDisabledOrgs.map((orgId) => parseInt(orgId)); + // callback return true if the userOrg is in the OPT OUT disabled org list + callback( + !!userOrgs.find((orgId) => orgsToCompare.indexOf(parseInt(orgId)) !== -1) + ); + }); +}; emproObj.prototype.init = function () { - tnthAjax.getCurrentUser((data) => { - if (!data || !data.id) return; - this.userId = data.id; - + const self = this; + this.setLoadingVis(true); + CurrentUserObj.initCurrentUser(() => { + this.userId = CurrentUserObj.getUserId(); + this.userOrgs = CurrentUserObj.getUserOrgs(); const isDebugging = getUrlParameter("debug"); - - this.setLoadingVis(true); - + if (!this.userId || !this.userOrgs || !this.userOrgs.length) { + this.setLoadingVis(false); + return; + } tnthAjax.getUserResearchStudies(this.userId, "patient", false, (data) => { if ( !isDebugging && @@ -494,6 +524,7 @@ emproObj.prototype.processTriggerData = function (data) { for (let q in data.triggers.domain[key]) { // if sequence count >= 3, the user can choose to opt_out of respective domain if ( + !self.optOutNotAllowed && q === "_sequential_hard_trigger_count" && parseInt(data.triggers.domain[key][q]) >= 3 ) { @@ -529,34 +560,37 @@ emproObj.prototype.initTriggerDomains = function (params, callbackFunc) { callback({ error: true }); return; } - //var self = this; const isDebugging = getUrlParameter("debug"); - tnthAjax.getSubStudyTriggers(this.userId, params, (data) => { - if (isDebugging) { - data = TestTriggersJson; - } - console.log("Trigger data: ", data); - if (!data || data.error || !data.triggers || !data.triggers.domain) { - callback({ error: true, reason: "no trigger data" }); - return false; - } + this.checkUserOrgAllowOptOut(this.userId, this.userOrgs, (isOptOutDisabled) => { + this.optOutNotAllowed = isOptOutDisabled; + console.log("Opt out is disabled ", isOptOutDisabled); + tnthAjax.getSubStudyTriggers(this.userId, params, (data) => { + if (isDebugging) { + data = TestTriggersJson; + } + console.log("Trigger data: ", data); + if (!data || data.error || !data.triggers || !data.triggers.domain) { + callback({ error: true, reason: "no trigger data" }); + return false; + } - this.processTriggerData(data); + this.processTriggerData(data); - /* - * display user domain topic(s) - */ - this.populateDomainDisplay(); - /* - * show/hide sections based on triggers - */ - this.initTriggerItemsVis(); + /* + * display user domain topic(s) + */ + this.populateDomainDisplay(); + /* + * show/hide sections based on triggers + */ + this.initTriggerItemsVis(); - callback(data); + callback(data); - //console.log("self.domains? ", self.domains); - //console.log("has hard triggers ", self.hasHardTrigger); - //console.log("has soft triggers ", self.hasSoftTrigger); + //console.log("self.domains? ", self.domains); + //console.log("has hard triggers ", self.hasHardTrigger); + //console.log("has soft triggers ", self.hasSoftTrigger); + }); }); }; let EmproObj = new emproObj(); diff --git a/portal/static/js/src/mixins/CurrentUser.js b/portal/static/js/src/mixins/CurrentUser.js index beac8bbb5..c36119cb5 100644 --- a/portal/static/js/src/mixins/CurrentUser.js +++ b/portal/static/js/src/mixins/CurrentUser.js @@ -95,6 +95,9 @@ var CurrentUser = { /* global $ i18next */ getRoleType: function() { return this.isPatientUser()?"patient":"staff"; }, + getUserResearchStudies: function() { + return this.userResearchStudyIds; + }, setUserResearchStudies: function(callback) { callback = callback || function() {}; tnthAjax.getUserResearchStudies(this.userId, this.getRoleType(), "", data => { diff --git a/portal/static/js/src/profile.js b/portal/static/js/src/profile.js index 61fc5d526..73883df77 100644 --- a/portal/static/js/src/profile.js +++ b/portal/static/js/src/profile.js @@ -1581,8 +1581,9 @@ export default (function() { return this.hasSubStudyTriggers() && this.getPostTxActionStatus() === "missed"; }, isPostTxActionRequired: function() { + const actionStatus = this.getPostTxActionStatus(); return this.subStudyTriggers.data && - (["due", "overdue", "required"].indexOf(this.getPostTxActionStatus()) !== -1 + (["due", "overdue", "required"].indexOf(actionStatus) !== -1 ); }, isPostTxActionNotApplicable: function() { diff --git a/portal/templates/profile/profile_macros.html b/portal/templates/profile/profile_macros.html index cbe8a9ee4..a806745c4 100644 --- a/portal/templates/profile/profile_macros.html +++ b/portal/templates/profile/profile_macros.html @@ -890,10 +890,10 @@

{% if person and person.username %}{{ _("for") + " " + pe
-
{{_("Actions Required (for Clinicians/Site Staff)")}}
+
{{_("Actions Required (for Clinicians/Site Staff)")}}
{{_("EMPRO questionnaire completed on:")}}
-

{{_("ACTION REQUIRED for high distress areas:")}}

+

{{_("ACTION REQUIRED for high distress areas:")}}

{{_("High distress areas")}}

  • diff --git a/portal/views/portal.py b/portal/views/portal.py index 648e9efd1..2962581eb 100644 --- a/portal/views/portal.py +++ b/portal/views/portal.py @@ -908,6 +908,7 @@ def config_settings(config_key): 'LOCALIZED_AFFILIATE_ORG', 'LR_', 'MAINTENANCE_', + 'OPT_OUT_DISABLED_ORG_IDS', 'PROTECTED_FIELDS', 'PROTECTED_ORG', 'PATIENT_LIST_ADDL_FIELDS', From 1318fb3df2b7fd187c68670c97fa06701e978f85 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Mon, 15 Apr 2024 16:27:27 -0700 Subject: [PATCH 099/143] moved `OPT_OUT_DELAY` to 30 mins as per TN-3238 --- portal/trigger_states/empro_states.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/portal/trigger_states/empro_states.py b/portal/trigger_states/empro_states.py index 823cfcc84..996e6a80f 100644 --- a/portal/trigger_states/empro_states.py +++ b/portal/trigger_states/empro_states.py @@ -26,7 +26,7 @@ from ..timeout_lock import TimeoutLock, LockTimeout EMPRO_LOCK_KEY = "empro-trigger-state-lock-{user_id}" -OPT_OUT_DELAY = 300 # seconds to allow user to provide opt-out choices +OPT_OUT_DELAY = 1800 # seconds to allow user to provide opt-out choices class EMPRO_state(StateMachine): """States a user repeatedly transitions through as an EMPRO participant From 46d63dd4d42fd89e96bd2c03f3d1a4f7fdbff734 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Mon, 15 Apr 2024 19:05:01 -0700 Subject: [PATCH 100/143] resolving testing issue with missed transitions to `resolved` and catching any outstanding action states when new EMPRO is posted. --- portal/trigger_states/empro_states.py | 21 ++++------------- portal/trigger_states/models.py | 33 +++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/portal/trigger_states/empro_states.py b/portal/trigger_states/empro_states.py index 996e6a80f..9ef17d771 100644 --- a/portal/trigger_states/empro_states.py +++ b/portal/trigger_states/empro_states.py @@ -231,23 +231,6 @@ def evaluate_triggers(qnr): current_app.logger.debug( "persist-trigger_states-new record state change to 'processed' " f"from evaluate_triggers() {ts}") - - # a patient submission closes the window of availability for the - # post-intervention clinician follow up from any previous visits. - # mark state if one is found - previous = TriggerState.query.filter( - TriggerState.user_id == qnr.subject_id).filter( - TriggerState.state == 'resolved').order_by( - TriggerState.timestamp.desc()).first() - if previous and previous.triggers.get('action_state') not in ( - 'completed', 'missed', 'not applicable', 'withdrawn'): - triggers = copy.deepcopy(previous.triggers) - triggers['action_state'] = 'missed' - previous.triggers = triggers - current_app.logger.debug( - f"persist-trigger_states-change previous {previous}") - db.session.commit() - return ts except TransitionNotAllowed as e: @@ -585,6 +568,10 @@ def extract_observations(questionnaire_response_id, override_state=False): "persist-trigger_states-new from" f" enter_user_trigger_critical_section() {ts}") + # As we now have a new EMPRO to score, clean up any unfinished + # rows, as they can no longer be acted on. + ts.resolve_outstanding(ts.visit_month) + if not ts.state == 'inprocess': raise ValueError( f"invalid state; can't score: {qnr.subject_id}:{qnr.id}") diff --git a/portal/trigger_states/models.py b/portal/trigger_states/models.py index fe1ef9b27..53c820c87 100644 --- a/portal/trigger_states/models.py +++ b/portal/trigger_states/models.py @@ -234,6 +234,39 @@ def latest_for_visit(patient_id, visit_month): TriggerState.visit_month == visit_month).order_by( TriggerState.id.desc()).first() + def resolve_outstanding(self, visit_month): + """resolve any visits prior to visit_month during transition + + Once the next EMPRO is posted, the window closes for any outstanding + clinician follow-ups. Clean up state if any such rows are found + """ + from .empro_states import EMPRO_state + + # a patient submission closes the window of availability for the + # post-intervention clinician follow up from any previous visits. + # mark state if one is found + outstanding = TriggerState.query.filter( + TriggerState.user_id == self.user_id).filter( + TriggerState.state.in_('triggered', 'resolved')).filter( + TriggerState.visit_month < visit_month) + for row in outstanding: + dirty = False + if row.state == 'triggered': + sm = EMPRO_state(row) + sm.resolve() + dirty = True + + if row.triggers.get('action_state') not in ( + 'completed', 'missed', 'not applicable', 'withdrawn'): + triggers = deepcopy(row.triggers) + triggers['action_state'] = 'missed' + row.triggers = triggers + dirty = True + current_app.logger.debug( + f"persist-trigger_states-change outstanding {row}") + if dirty: + db.session.commit() + class TriggerStatesReporting: """Manage reporting details for a given patient""" From f2d8d5866f04febd0f177035991074067aff80e2 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Mon, 15 Apr 2024 19:16:58 -0700 Subject: [PATCH 101/143] TN-3281 add `opted_out_domains` column to adherence report --- portal/models/reporting.py | 3 +++ portal/trigger_states/models.py | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/portal/models/reporting.py b/portal/models/reporting.py index 2bd142cb0..65b493652 100644 --- a/portal/models/reporting.py +++ b/portal/models/reporting.py @@ -141,6 +141,8 @@ def empro_row_detail(row, ts_reporting): or "") ht = ts_reporting.hard_triggers_for_visit(visit_month) row['hard_trigger_domains'] = ', '.join(ht) if ht else "" + oo = ts_reporting.soft_triggers_for_visit(visit_month) + row['opted_out_domains'] = ', '.join(oo) if oo else "" st = ts_reporting.soft_triggers_for_visit(visit_month) row['soft_trigger_domains'] = ', '.join(st) if st else "" da = ts_reporting.domains_accessed(visit_month) @@ -447,6 +449,7 @@ def patient_generator(): 'EMPRO_questionnaire_completion_date', 'soft_trigger_domains', 'hard_trigger_domains', + 'opted_out_domains', 'content_domains_accessed', 'clinician', 'clinician_status', diff --git a/portal/trigger_states/models.py b/portal/trigger_states/models.py index 53c820c87..93571933c 100644 --- a/portal/trigger_states/models.py +++ b/portal/trigger_states/models.py @@ -380,6 +380,17 @@ def soft_triggers_for_visit(self, visit_month): if ts: return ts.soft_trigger_list() + def opted_out_domains_for_visit(self, visit_month): + """Return list of opted-out domains for given visit month + + :param visit_month: zero indexed month value + :returns list of domains user opted-out of, or None if n/a + + """ + ts = self.latest_by_visit[visit_month] + if ts: + return ts.opted_out_domains() + def rebuild_trigger_states(patient): """If a user's consent moves, need to re-build the trigger states for user From 68754b1c01c72436d9fd337d46d7a07df6f7f9c9 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Mon, 15 Apr 2024 19:40:22 -0700 Subject: [PATCH 102/143] more logging around delay bug, thinking timezone differences may be the problem --- portal/trigger_states/empro_states.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/portal/trigger_states/empro_states.py b/portal/trigger_states/empro_states.py index 9ef17d771..8762eca14 100644 --- a/portal/trigger_states/empro_states.py +++ b/portal/trigger_states/empro_states.py @@ -273,7 +273,9 @@ def delay_processing(ts): # check time since row transitioned to current state. delay # till threshold reached - current_app.logger.debug(f"QQQ {ts.timestamp + timedelta(seconds=OPT_OUT_DELAY)} {datetime.utcnow()}") + current_app.logger.debug(f"QQQ row timestamp: {ts.timestamp} now: {datetime.utcnow()}") + current_app.logger.debug(f"QQQ row + delay {ts.timestamp + timedelta(seconds=OPT_OUT_DELAY)}") + current_app.logger.debug(f"QQQ tzinfo: {ts.timestamp.tzinfo} tz2: {(ts.timestamp + timedelta(seconds=OPT_OUT_DELAY)).tzinfo} tz3: {datetime.utcnow().tzinfo}") if ts.timestamp < timedelta(seconds=OPT_OUT_DELAY) + datetime.utcnow(): current_app.logger.debug(f"QQQ return True from delay_processing") return True From 00b211936e76bddc51699b1fad559047c89463c4 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Tue, 16 Apr 2024 08:47:30 -0700 Subject: [PATCH 103/143] typo - list for sql.in_ clause needs another set of parens. --- portal/trigger_states/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/portal/trigger_states/models.py b/portal/trigger_states/models.py index 93571933c..74c7d106a 100644 --- a/portal/trigger_states/models.py +++ b/portal/trigger_states/models.py @@ -247,7 +247,7 @@ def resolve_outstanding(self, visit_month): # mark state if one is found outstanding = TriggerState.query.filter( TriggerState.user_id == self.user_id).filter( - TriggerState.state.in_('triggered', 'resolved')).filter( + TriggerState.state.in_(('triggered', 'resolved'))).filter( TriggerState.visit_month < visit_month) for row in outstanding: dirty = False From f786f57c5e38d76b0918db7aef8297eaf07db21f Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Tue, 16 Apr 2024 11:09:44 -0700 Subject: [PATCH 104/143] post tx questionnaire submission error check fix, minor styling fix --- portal/static/js/src/modules/TnthAjax.js | 4 ++++ portal/static/less/eproms.less | 11 ++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/portal/static/js/src/modules/TnthAjax.js b/portal/static/js/src/modules/TnthAjax.js index 545357c90..593d73510 100644 --- a/portal/static/js/src/modules/TnthAjax.js +++ b/portal/static/js/src/modules/TnthAjax.js @@ -1121,6 +1121,10 @@ export default { /*global $ */ params = params || {}; params.data = JSON.stringify(data); this.sendRequest("/api/patient/" + userId + "/assessment", "POST", userId, params, function(data) { + if (data && data.error) { + callback({"error": true}); + return; + } callback({data: data}); }); }, diff --git a/portal/static/less/eproms.less b/portal/static/less/eproms.less index fc2abb623..3d01e0fc6 100644 --- a/portal/static/less/eproms.less +++ b/portal/static/less/eproms.less @@ -4055,7 +4055,11 @@ section.header { #postTxQuestionnaireContainer { #triggersSection { .list { - width: 50%; + width: 100%; + display: grid; + align-content: space-evenly; + grid-template-columns: repeat(2, 180px); + gap: 8px; } } } @@ -4064,10 +4068,7 @@ section.header { #postTxQuestionnaireContainer { #triggersSection { .list { - width: 60%; - -webkit-columns: 3; - -moz-columns: 3; - columns: 3; + grid-template-columns: repeat(3, 180px); } } } From e9ed5ef5b37f383eec5fe82f5e992770b0d837b4 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Tue, 16 Apr 2024 12:02:05 -0700 Subject: [PATCH 105/143] Another time warp issue - don't write a second `due` row out if one already exists. --- portal/trigger_states/empro_states.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/portal/trigger_states/empro_states.py b/portal/trigger_states/empro_states.py index 8762eca14..99f901cf8 100644 --- a/portal/trigger_states/empro_states.py +++ b/portal/trigger_states/empro_states.py @@ -145,9 +145,19 @@ def initiate_trigger(user_id, as_of_date=None, rebuilding=False): # `due` row that was found above. visit_month = lookup_visit_month(user_id, as_of_date) if ts.visit_month != visit_month: - current_app.logger.warn(f"{user_id} skipped EMPRO visit {ts.visit_month}") - ts.visit_month = visit_month - ts.timestamp = as_of_date + # another test case, can't persist 2 rows with same user, visit, state + # confirm time warp isn't overlapping with existing + already_there = TriggerState.query.filter( + TriggerState.visit_month == visit_month).filter( + TriggerState.state == 'due').filter( + TriggerState.user_id == user_id).first() + if already_there is not None: + already_there.timestamp = as_of_date + ts = already_there + else: + current_app.logger.warn(f"{user_id} skipped EMPRO visit {ts.visit_month}") + ts.visit_month = visit_month + ts.timestamp = as_of_date db.session.commit() # Allow idempotent call - skip out if in correct state From c53198e3129abd2aa7b98602f9ae5d22a06042a3 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Tue, 16 Apr 2024 16:08:44 -0700 Subject: [PATCH 106/143] revert symptom of broken timewarp fix. --- portal/trigger_states/empro_states.py | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/portal/trigger_states/empro_states.py b/portal/trigger_states/empro_states.py index 99f901cf8..c9ca2bba6 100644 --- a/portal/trigger_states/empro_states.py +++ b/portal/trigger_states/empro_states.py @@ -105,6 +105,7 @@ def users_trigger_state(user_id, as_of_date=None): for ts_row in rows: # most recent with a timestamp prior to as_of_date, in case this is a rebuild if as_of_date < ts_row.timestamp: + current_app.logger.debug(f"skipping over newer trigger_states({ts_row.id}) row") continue ts = ts_row if ts.visit_month < vm and not withdrawal_date: @@ -137,7 +138,7 @@ def initiate_trigger(user_id, as_of_date=None, rebuilding=False): if as_of_date is None: as_of_date = datetime.utcnow() - ts = users_trigger_state(user_id) + ts = users_trigger_state(user_id, as_of_date) if ts.state == 'due': # Possible the user took no action, as in skipped the last month # (or multiple months may have been skipped if time-warping). @@ -145,22 +146,12 @@ def initiate_trigger(user_id, as_of_date=None, rebuilding=False): # `due` row that was found above. visit_month = lookup_visit_month(user_id, as_of_date) if ts.visit_month != visit_month: - # another test case, can't persist 2 rows with same user, visit, state - # confirm time warp isn't overlapping with existing - already_there = TriggerState.query.filter( - TriggerState.visit_month == visit_month).filter( - TriggerState.state == 'due').filter( - TriggerState.user_id == user_id).first() - if already_there is not None: - already_there.timestamp = as_of_date - ts = already_there - else: - current_app.logger.warn(f"{user_id} skipped EMPRO visit {ts.visit_month}") - ts.visit_month = visit_month - ts.timestamp = as_of_date + current_app.logger.warn(f"{user_id} skipped EMPRO visit {ts.visit_month}") + ts.visit_month = visit_month + ts.timestamp = as_of_date db.session.commit() - # Allow idempotent call - skip out if in correct state + # Allow idempotent call - skip out when in correct state return ts # Check and update the status of pending work. From b8402f4ec33423595ace5c43b8a9706682c2aacd Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Tue, 16 Apr 2024 16:58:36 -0700 Subject: [PATCH 107/143] update `purge_user` to work with additional constraints. (needed in this PR as a few test users are in a broken state) --- portal/models/user.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/portal/models/user.py b/portal/models/user.py index 517c33ebc..5ab9b7387 100644 --- a/portal/models/user.py +++ b/portal/models/user.py @@ -142,6 +142,7 @@ def permanently_delete_user( "Contradicting username and user_id values given") def purge_user(user, acting_user): + from ..trigger_states.models import TriggerState if not user: raise ValueError("No such user: {}".format(username)) if acting_user.id == user.id: @@ -158,6 +159,17 @@ def purge_user(user, acting_user): for t in tous: db.session.delete(t) + TriggerState.query.filter(TriggerState.user_id == user.id).delete() + + # possible this user generated a temp user for auth flows - that + # user's deleted audit record holds a key to the user being purged. + # update to that user id + auds = Audit.query.filter(Audit.user_id == user.id).filter( + Audit.subject_id != user.id) + for a in auds: + if a.comment.startswith("marking deleted user"): + a.user_id = a.subject_id + # the rest should die on cascade rules db.session.delete(user) db.session.commit() From ae10c5c776e349be8a683a8a3083dfdbdaae3d81 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Tue, 16 Apr 2024 18:10:15 -0700 Subject: [PATCH 108/143] accumulate number of times a user has opted out of any given domain in `_total_opted_out` --- portal/trigger_states/models.py | 28 +++++++++++++++++++++++----- tests/test_trigger_states.py | 7 +++++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/portal/trigger_states/models.py b/portal/trigger_states/models.py index 74c7d106a..726352c4e 100644 --- a/portal/trigger_states/models.py +++ b/portal/trigger_states/models.py @@ -112,18 +112,36 @@ def apply_opt_out(self, opt_out_dict): if vals.get(opt_out_this_visit_key) is True: opt_out_of_domains.add(d) + if not opt_out_of_domains: + # no changes to persist + return self + tc = deepcopy(self.triggers) for domain, link_triggers in tc['domain'].items(): if domain in opt_out_of_domains: link_triggers[opt_out_this_visit_key] = True - opt_out_of_domains.remove(domain) elif opt_out_this_visit_key in link_triggers: link_triggers.pop(opt_out_this_visit_key) - if opt_out_of_domains: - raise ValueError( - f"user_id({self.user_id}):visit_month({self.visit_month}) missing domains " - f"requested in opt_out: {opt_out_of_domains}") + # Given the business rule to only allow 3 total opt-outs, bump counts + # which requires full trigger history + total_opt_outs_by_domain = {domain: 1 for domain in opt_out_of_domains} + previous_visits = TriggerState.query.filter( + TriggerState.visit_month < self.visit_month).filter( + TriggerState.user_id == self.user_id).filter( + TriggerState.state == 'resolved').order_by( + TriggerState.visit_month) + + for row in previous_visits: + # accumulate counts of previous opt-outs for current request + for domain, link_triggers in row.triggers['domain']: + if domain not in opt_out_of_domains: + continue + if link_triggers.get(opt_out_this_visit_key, False): + total_opt_outs_by_domain[domain] += 1 + + for domain, count in total_opt_outs_by_domain.items(): + tc['domain'][domain]['_total_opted_out'] = count self.triggers = tc return self diff --git a/tests/test_trigger_states.py b/tests/test_trigger_states.py index fe5bee7f4..9356b39ad 100644 --- a/tests/test_trigger_states.py +++ b/tests/test_trigger_states.py @@ -190,6 +190,13 @@ def test_apply_opt_out(initialized_patient, processed_ts, opt_out_submission): found = [k for k, v in result.triggers['domain'].items() if opt_out_this_visit_key in v] assert len(found) == 2 + # confirm running counts for `_total_opted_out` match input + for domain, data in result.triggers['domain'].items(): + if domain in ('fatigue', 'general_pain'): + assert data['_total_opted_out'] == 1 + else: + assert '_total_opted_out' not in data.keys() + def test_opted_out(mock_triggers): ts = TriggerState(state='processed', triggers=mock_triggers, user_id=1) From 09881f8fb14ac09d1c7ca9e4dbce27f65dd9efe4 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Wed, 17 Apr 2024 11:59:54 -0700 Subject: [PATCH 109/143] missed request for `items()` on dictionary iteration. --- portal/trigger_states/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/portal/trigger_states/models.py b/portal/trigger_states/models.py index 726352c4e..70018ad8a 100644 --- a/portal/trigger_states/models.py +++ b/portal/trigger_states/models.py @@ -134,7 +134,7 @@ def apply_opt_out(self, opt_out_dict): for row in previous_visits: # accumulate counts of previous opt-outs for current request - for domain, link_triggers in row.triggers['domain']: + for domain, link_triggers in row.triggers['domain'].items(): if domain not in opt_out_of_domains: continue if link_triggers.get(opt_out_this_visit_key, False): From 495d2a419d3a87227b437dcd05e3cfae3813caf4 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Wed, 17 Apr 2024 13:12:40 -0700 Subject: [PATCH 110/143] add `partially opted out` versions of the empro clinician trigger emails. --- portal/config/eproms/AppText.json | 10 ++++++++++ portal/trigger_states/empro_messages.py | 2 ++ 2 files changed, 12 insertions(+) diff --git a/portal/config/eproms/AppText.json b/portal/config/eproms/AppText.json index f13910021..598a8f928 100644 --- a/portal/config/eproms/AppText.json +++ b/portal/config/eproms/AppText.json @@ -190,6 +190,11 @@ "name": "empro clinician trigger notification", "resourceType": "AppText" }, + { + "custom_text": "{config[LR_ORIGIN]}/c/portal/truenth/asset/mail?version=latest&uuid=faab8398-fa93-f1ca-9d81-3ddc38769d96", + "name": "empro clinician trigger notification partially opted out", + "resourceType": "AppText" + }, { "custom_text": "{config[LR_ORIGIN]}/c/portal/truenth/asset/mail?version=latest&uuid=73361f9b-f79a-ddaa-7437-c304af8953ef", "name": "empro clinician trigger notification all opted out", @@ -200,6 +205,11 @@ "name": "empro clinician trigger reminder", "resourceType": "AppText" }, + { + "custom_text": "{config[LR_ORIGIN]}/c/portal/truenth/asset/mail?version=latest&uuid=86b1c0ef-e3ee-e264-0190-71678d21b6a5", + "name": "empro clinician trigger reminder partially opted out", + "resourceType": "AppText" + }, { "custom_text": "

    (greeting),

    This email was sent to you because you are a patient at (clinic name) and consented to participate in the Prostate Cancer Outcomes - (parent org) Registry Study.

    This is an invitation to use the TrueNTH website, where you will report on your health. Your participation will help us collectively improve the care that men receive during their prostate cancer journey.

    To complete your first questionnaire, please first verify your account.

    You can also access the TrueNTH website with this link:

    {0}

    Save this email so that you can return to TrueNTH any time.

    If you have any queries, please contact your representative at (clinic name).

    ", "name": "profileSendEmail invite email_body", diff --git a/portal/trigger_states/empro_messages.py b/portal/trigger_states/empro_messages.py index 77093485a..3e424a311 100644 --- a/portal/trigger_states/empro_messages.py +++ b/portal/trigger_states/empro_messages.py @@ -105,6 +105,8 @@ def staff_emails(patient, hard_triggers, opted_out_domains, initial_notification if not (set(hard_triggers) - set(opted_out_domains)): # All triggered were opted out of - pick up different email template app_text_name += " all opted out" + elif opted_out_domains: + app_text_name += " partially opted out" # According to spec, args need at least: # - study ID From 6a49b783e192f1a5373eeecbb0e3889b09864fa3 Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Wed, 17 Apr 2024 13:55:30 -0700 Subject: [PATCH 111/143] update UUIDs for emails --- portal/config/eproms/AppText.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/portal/config/eproms/AppText.json b/portal/config/eproms/AppText.json index 598a8f928..a2f2c44ed 100644 --- a/portal/config/eproms/AppText.json +++ b/portal/config/eproms/AppText.json @@ -186,27 +186,27 @@ "resourceType": "AppText" }, { - "custom_text": "{config[LR_ORIGIN]}/c/portal/truenth/asset/mail?version=latest&uuid=faab8398-fa93-f1ca-9d81-3ddc38769d96", + "custom_text": "{config[LR_ORIGIN]}/c/portal/truenth/asset/mail?version=latest&uuid=fbef98f2-53be-316c-a671-c1ebb423ed33", "name": "empro clinician trigger notification", "resourceType": "AppText" }, { - "custom_text": "{config[LR_ORIGIN]}/c/portal/truenth/asset/mail?version=latest&uuid=faab8398-fa93-f1ca-9d81-3ddc38769d96", + "custom_text": "{config[LR_ORIGIN]}/c/portal/truenth/asset/mail?version=latest&uuid=a1f1a57c-b250-0cf5-548b-5b0284e82ae7", "name": "empro clinician trigger notification partially opted out", "resourceType": "AppText" }, { - "custom_text": "{config[LR_ORIGIN]}/c/portal/truenth/asset/mail?version=latest&uuid=73361f9b-f79a-ddaa-7437-c304af8953ef", + "custom_text": "{config[LR_ORIGIN]}/c/portal/truenth/asset/mail?version=latest&uuid=e875dceb-58d9-c8ce-3f44-6b95503e725a", "name": "empro clinician trigger notification all opted out", "resourceType": "AppText" }, { - "custom_text": "{config[LR_ORIGIN]}/c/portal/truenth/asset/mail?version=latest&uuid=86b1c0ef-e3ee-e264-0190-71678d21b6a5", + "custom_text": "{config[LR_ORIGIN]}/c/portal/truenth/asset/mail?version=latest&uuid=3d1ab756-fe6c-eef5-5eda-78f17ac669ee", "name": "empro clinician trigger reminder", "resourceType": "AppText" }, { - "custom_text": "{config[LR_ORIGIN]}/c/portal/truenth/asset/mail?version=latest&uuid=86b1c0ef-e3ee-e264-0190-71678d21b6a5", + "custom_text": "{config[LR_ORIGIN]}/c/portal/truenth/asset/mail?version=latest&uuid=76eff370-1b98-9387-bbfd-94ffc199ed49", "name": "empro clinician trigger reminder partially opted out", "resourceType": "AppText" }, From 776d10eb78353e943121ac5f3d958e414567e0f9 Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Fri, 19 Apr 2024 09:36:47 -0700 Subject: [PATCH 112/143] OPT OUT frontend changes - check for max opt out limit (#4376) part of https://movember.atlassian.net/browse/TN-3284 address OPT OUT requirement: `A patient can choose to opt out a maximum of 3 times for a single domain` Changes include: Frontend will now check triggers history to ensure that user had not chosen to opt out of a domain at least 3 times in the past, i.e. check `_total_opted_out` of the domain property. If so, it will not present the domain as an opt out option on UI. --------- Co-authored-by: Amy Chen --- portal/static/js/src/empro.js | 154 ++++++++++++------ portal/static/js/src/profile.js | 6 + portal/static/less/eproms.less | 12 +- portal/templates/profile/patient_profile.html | 2 +- portal/templates/profile/profile_macros.html | 26 ++- 5 files changed, 138 insertions(+), 62 deletions(-) diff --git a/portal/static/js/src/empro.js b/portal/static/js/src/empro.js index cb9f3b0bc..4f327cb24 100644 --- a/portal/static/js/src/empro.js +++ b/portal/static/js/src/empro.js @@ -220,6 +220,7 @@ emproObj.prototype.handleSubmitOptoutData = function () { { data: JSON.stringify(EmproObj.optOutSubmitData), max_attempts: 3, + timeout: 10000 // 10 seconds }, (data) => { return EmproObj.onAfterSubmitOptoutData(data); @@ -330,18 +331,22 @@ emproObj.prototype.initTriggerItemsVis = function () { return; } }; -emproObj.prototype.checkUserOrgAllowOptOut = function (userId, userOrgs, callback) { +emproObj.prototype.checkUserOrgAllowOptOut = function ( + userId, + userOrgs, + callback +) { callback = callback || function () {}; if (!userId || !userOrgs || !userOrgs.length) { callback(false); return; } const OPT_OUT_ORGS_KEY = "OPT_OUT_DISABLED_ORG_IDS"; - // get opt out disabled orgs from setting - tnthAjax.setting(OPT_OUT_ORGS_KEY, userId, null, (data) => { + // get opt out disabled orgs from setting + tnthAjax.setting(OPT_OUT_ORGS_KEY, userId, null, (data) => { if (!data || !data[OPT_OUT_ORGS_KEY]) { - callback(false); - return; + callback(false); + return; } const optOutDisabledOrgs = data[OPT_OUT_ORGS_KEY]; if (!optOutDisabledOrgs.length) { @@ -388,8 +393,10 @@ emproObj.prototype.init = function () { EPROMS_SUBSTUDY_QUESTIONNAIRE_IDENTIFIER, (data) => { if (isDebugging) { - data = TestResponsesJson; - data.entry[0].authored = new Date().toISOString(); + if (!data) { + data = TestResponsesJson; + } + } console.log("Questionnaire response data: ", data); // no questionnaire data, just return here @@ -403,6 +410,10 @@ emproObj.prototype.init = function () { let assessmentData = data.entry.sort(function (a, b) { return new Date(b.authored) - new Date(a.authored); }); + if (isDebugging) { + assessmentData[0].authored = new Date().toISOString(); + assessmentData[0].status = "completed"; + } let assessmentDate = assessmentData[0]["authored"]; let [today, authoredDate, status, identifier] = [ tnthDate.getDateWithTimeZone(new Date(), "yyyy-mm-dd"), @@ -493,14 +504,22 @@ emproObj.prototype.setLoadingVis = function (loading) { } $(LOADING_INDICATOR_ID).removeClass("hide"); }; -emproObj.prototype.processTriggerData = function (data) { +emproObj.prototype.processTriggerData = function (data, historyData) { if (!data || data.error || !data.triggers || !data.triggers.domain) { - //this.initThankyouModal(false); console.log("No trigger data"); return false; } var self = this; + let processedHistoryData = []; + if (historyData) { + processedHistoryData = historyData + .filter((item) => item.triggers && item.triggers.domain) + .map((item) => item.triggers.domain); + } + + console.log("processed history data ", processedHistoryData); + // set visit month related to trigger data this.visitMonth = data.visit_month; @@ -522,17 +541,6 @@ emproObj.prototype.processTriggerData = function (data) { self.mappedDomains.push(mappedDomain); } for (let q in data.triggers.domain[key]) { - // if sequence count >= 3, the user can choose to opt_out of respective domain - if ( - !self.optOutNotAllowed && - q === "_sequential_hard_trigger_count" && - parseInt(data.triggers.domain[key][q]) >= 3 - ) { - // console.log("domain? ", key, " sequence ", parseInt(data.triggers.domain[key][q])); - if (self.optOutDomains.indexOf(key) === -1) { - self.optOutDomains.push(key); - } - } if (data.triggers.domain[key][q] === "hard") { self.hasHardTrigger = true; /* @@ -551,6 +559,26 @@ emproObj.prototype.processTriggerData = function (data) { self.softTriggerDomains.push(key); } } + + if (self.optOutNotAllowed) { + continue; + } + const MAX_ALLOWED_OPT_OUT_NUM = 3; + // check if user has chosen to opt out this domain 3 times before + const hasReachedMaxOptOut = processedHistoryData.find( + (item) => parseInt(item[key]["_total_opted_out"]) >= MAX_ALLOWED_OPT_OUT_NUM + ); + // if sequence count >= 3, the user can choose to opt_out of respective domain + if ( + !hasReachedMaxOptOut && + q === "_sequential_hard_trigger_count" && + parseInt(data.triggers.domain[key][q]) >= 3 + ) { + // console.log("domain? ", key, " sequence ", parseInt(data.triggers.domain[key][q])); + if (self.optOutDomains.indexOf(key) === -1) { + self.optOutDomains.push(key); + } + } } } }; @@ -561,37 +589,63 @@ emproObj.prototype.initTriggerDomains = function (params, callbackFunc) { return; } const isDebugging = getUrlParameter("debug"); - this.checkUserOrgAllowOptOut(this.userId, this.userOrgs, (isOptOutDisabled) => { - this.optOutNotAllowed = isOptOutDisabled; - console.log("Opt out is disabled ", isOptOutDisabled); - tnthAjax.getSubStudyTriggers(this.userId, params, (data) => { - if (isDebugging) { - data = TestTriggersJson; - } - console.log("Trigger data: ", data); - if (!data || data.error || !data.triggers || !data.triggers.domain) { - callback({ error: true, reason: "no trigger data" }); - return false; - } - - this.processTriggerData(data); - - /* - * display user domain topic(s) - */ - this.populateDomainDisplay(); - /* - * show/hide sections based on triggers - */ - this.initTriggerItemsVis(); - - callback(data); + this.checkUserOrgAllowOptOut( + this.userId, + this.userOrgs, + (isOptOutDisabled) => { + this.optOutNotAllowed = isOptOutDisabled; + console.log("Opt out is disabled ", isOptOutDisabled); + Promise.allSettled([ + // current triggers + new Promise((resolve, reject) => + tnthAjax.getSubStudyTriggers(this.userId, params, (data) => { + if (data && data.error) { + reject({ error: true }); + return; + } + resolve(data); + }) + ), + // trigger history + new Promise((resolve, reject) => + tnthAjax.getTriggersHistory(this.userId, params, (data) => { + if (data && data.error) { + reject({ error: true }); + return; + } + resolve(data); + }) + ), + ]).then((results) => { + const currentTriggerData = + results[0] && results[0].status === "fulfilled" && results[0].value + ? results[0].value + : null; + const historyTriggerData = + results[1] && results[1].status === "fulfilled" && results[1].value + ? results[1].value + : null; + if (isDebugging && !currentTriggerData) { + currentTriggerData = TestTriggersJson; + } + if (!currentTriggerData) { + callback({ error: true, reason: "no trigger data"}); + return false; + } + this.processTriggerData(currentTriggerData, historyTriggerData); + /* + * display user domain topic(s) + */ + this.populateDomainDisplay(); + /* + * show/hide sections based on triggers + */ + this.initTriggerItemsVis(); - //console.log("self.domains? ", self.domains); - //console.log("has hard triggers ", self.hasHardTrigger); - //console.log("has soft triggers ", self.hasSoftTrigger); - }); - }); + callback(currentTriggerData); + }); + } + ); }; let EmproObj = new emproObj(); $(document).ready(function () { diff --git a/portal/static/js/src/profile.js b/portal/static/js/src/profile.js index 73883df77..f21c4fd48 100644 --- a/portal/static/js/src/profile.js +++ b/portal/static/js/src/profile.js @@ -1568,6 +1568,9 @@ export default (function() { this.hasSubStudyStatusErrors() ); }, + shouldShowPostTxError: function() { + return this.shouldDisableSubstudyPostTx() && this.hasPostTxQuestionnaireErrors(); + }, getPostTxActionStatus: function() { if (!this.subStudyTriggers.data || !this.subStudyTriggers.data.action_state) { return ""; @@ -3598,6 +3601,9 @@ export default (function() { }[a]; }); } + }, + refresh: function() { + window.location.reload(); } }); return ProfileObj; diff --git a/portal/static/less/eproms.less b/portal/static/less/eproms.less index 3d01e0fc6..091f9087d 100644 --- a/portal/static/less/eproms.less +++ b/portal/static/less/eproms.less @@ -3954,9 +3954,6 @@ section.header { align-items: flex-start; list-style-type: square; list-style-position: inside; - .text { - margin-bottom: 4px; - } } } #noTriggersSection { @@ -4090,9 +4087,9 @@ section.header { position: relative; .close { position: absolute; - right: 16px; - top: 8px; - font-size: 24px; + right: 24px; + top: 16px; + font-size: 2.8rem; display: block; font-weight: 400; color: @mutedColor; @@ -4253,6 +4250,7 @@ section.header { border-bottom: 0; &:first-of-type { padding-left: 0; + flex: 1.35; } &:not(:last-of-type) { border-right: 1px solid @HRColor; @@ -4340,6 +4338,8 @@ section.header { } .btn-default { border: 1px solid @emproBtnPrimaryColor; + font-size: 0.88em; + padding: 0.88em; } } diff --git a/portal/templates/profile/patient_profile.html b/portal/templates/profile/patient_profile.html index e9d80a8d1..f96be9c78 100644 --- a/portal/templates/profile/patient_profile.html +++ b/portal/templates/profile/patient_profile.html @@ -60,7 +60,7 @@
{%- if enrolled_in_substudy -%} - {{profile_macro.postInterventionQuestionnaire(user)}} + {{profile_macro.postInterventionQuestionnaire(user, current_user)}} {%- endif -%} {%- endif -%} {{profile_macro.profileDemoDetail(user, current_user)}} diff --git a/portal/templates/profile/profile_macros.html b/portal/templates/profile/profile_macros.html index a806745c4..73c51c47b 100644 --- a/portal/templates/profile/profile_macros.html +++ b/portal/templates/profile/profile_macros.html @@ -878,7 +878,18 @@

{% if person and person.username %}{{ _("for") + " " + pe

{{_("High distressed areas ( Do not contact ): ")}}

{% endmacro %} -{%- macro postInterventionQuestionnaire(person) -%} +{%- macro adminTriggerLinks(person, current_user) %} + {% if (current_user.has_role(ROLE.ADMIN.value)) %} +
+

ADMIN USE ONLY

+ +

+ Current Triggers | + Trigger History +

+ {% endif %} +{% endmacro %} +{%- macro postInterventionQuestionnaire(person, current_user) -%}
 
@@ -909,9 +920,9 @@

{{_("Actions Required (for C

+ {{postInterventionOptoutDomains()}}
{{_("No action required")}}
- {{postInterventionOptoutDomains()}}
{{_("View subject EMPRO report for more information")}}
@@ -993,11 +1004,16 @@

{{_("Actions Required (for C {{_("Action status:")}} -
- - +
+
+ + +
+
+
+ {{adminTriggerLinks(person, current_user)}}
{% endcall %} From a428403ea35a88f670b9a5e1ec11a9047faf9244 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Fri, 19 Apr 2024 10:59:33 -0700 Subject: [PATCH 113/143] patch test case, with audit entry not having a defined comment. --- portal/models/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/portal/models/user.py b/portal/models/user.py index 5ab9b7387..47591843e 100644 --- a/portal/models/user.py +++ b/portal/models/user.py @@ -167,7 +167,7 @@ def purge_user(user, acting_user): auds = Audit.query.filter(Audit.user_id == user.id).filter( Audit.subject_id != user.id) for a in auds: - if a.comment.startswith("marking deleted user"): + if a.comment and a.comment.startswith("marking deleted user"): a.user_id = a.subject_id # the rest should die on cascade rules From 8bf7b1d17f133f106747e96855eb3984d8829f1b Mon Sep 17 00:00:00 2001 From: pbugni Date: Fri, 19 Apr 2024 13:25:40 -0700 Subject: [PATCH 114/143] Pull ethnicity and race coding from filesystem (#4377) The previously available value sets for ethnicity and race, hosted at hl7.org, are no longer available for automated download. Dumped contents to filesystem for a reliable import on deploy. --- portal/config/eproms/Coding.json | 6 - portal/models/clinical_constants.py | 19 +- .../models/code_systems/v3-Ethnicity.cs.json | 274 + portal/models/code_systems/v3-Race.cs.json | 4846 +++++++++++++++++ 4 files changed, 5136 insertions(+), 9 deletions(-) create mode 100644 portal/models/code_systems/v3-Ethnicity.cs.json create mode 100644 portal/models/code_systems/v3-Race.cs.json diff --git a/portal/config/eproms/Coding.json b/portal/config/eproms/Coding.json index dfc712588..9c3f28fe6 100644 --- a/portal/config/eproms/Coding.json +++ b/portal/config/eproms/Coding.json @@ -5862,12 +5862,6 @@ "resourceType": "Coding", "system": "http://snomed.info/sct" }, - { - "code": "", - "display": "", - "resourceType": "Coding", - "system": "http://us.truenth.org/clinical-codes" - }, { "code": "111", "display": "biopsy", diff --git a/portal/models/clinical_constants.py b/portal/models/clinical_constants.py index 2df5892ac..502a8f5d3 100644 --- a/portal/models/clinical_constants.py +++ b/portal/models/clinical_constants.py @@ -1,4 +1,6 @@ """ TrueNTH Clinical Codes """ +import json +import os import requests from ..database import db @@ -90,12 +92,23 @@ def parse_concepts(elements, system): return ccs -def fetch_HL7_V3_Namespace(valueSet): +def fetch_HL7_V3_Namespace(valueSet, pull_from_hl7=False): """Pull and parse the published FHIR ethnicity namespace""" + # NB, this used to be pulled on every deploy, but hl7.org now requires human + # intervention, to bypass the captcha - now pulling cached version off file + # system. src_url = 'http://hl7.org/fhir/STU3/v3/{valueSet}/v3-{valueSet}.cs.json'.format( valueSet=valueSet) - response = requests.get(src_url) - return parse_concepts(response.json()['concept'], + if pull_from_hl7: + response = requests.get(src_url) + concept_source = response.json() + else: + cwd = os.path.dirname(__file__) + fp = os.path.join(cwd, f'code_systems/v3-{valueSet}.cs.json') + with open(fp, 'r') as jfile: + concept_source = json.load(jfile) + + return parse_concepts(concept_source['concept'], system='http://hl7.org/fhir/v3/{}'.format(valueSet)) diff --git a/portal/models/code_systems/v3-Ethnicity.cs.json b/portal/models/code_systems/v3-Ethnicity.cs.json new file mode 100644 index 000000000..780e8111b --- /dev/null +++ b/portal/models/code_systems/v3-Ethnicity.cs.json @@ -0,0 +1,274 @@ +{ + "resourceType": "CodeSystem", + "id": "v3-Ethnicity", + "meta": { + "lastUpdated": "2016-11-11T00:00:00.000+11:00" + }, + "text": { + "status": "generated", + "div": "

Release Date: 2016-11-11

\r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
LevelCodeDisplayDefinition
12135-2 Hispanic or Latino
2  2137-8 Spaniard
3    2138-6 Andalusian
3    2139-4 Asturian
3    2140-2 Castillian
3    2141-0 Catalonian
3    2142-8 Belearic Islander
3    2143-6 Gallego
3    2144-4 Valencian
3    2145-1 Canarian
3    2146-9 Spanish Basque
2  2148-5 Mexican
3    2149-3 Mexican American
3    2150-1 Mexicano
3    2151-9 Chicano
3    2152-7 La Raza
3    2153-5 Mexican American Indian
2  2155-0 Central American
3    2156-8 Costa Rican
3    2157-6 Guatemalan
3    2158-4 Honduran
3    2159-2 Nicaraguan
3    2160-0 Panamanian
3    2161-8 Salvadoran
3    2162-6 Central American Indian
3    2163-4 Canal Zone
2  2165-9 South American
3    2166-7 Argentinean
3    2167-5 Bolivian
3    2168-3 Chilean
3    2169-1 Colombian
3    2170-9 Ecuadorian
3    2171-7 Paraguayan
3    2172-5 Peruvian
3    2173-3 Uruguayan
3    2174-1 Venezuelan
3    2175-8 South American Indian
3    2176-6 Criollo
2  2178-2 Latin American
2  2180-8 Puerto Rican
2  2182-4 Cuban
2  2184-0 Dominican
12186-5 Not Hispanic or Latino\n Note that this term remains in the table for completeness, even though within HL7, the notion of "not otherwise coded" term is deprecated.
\r\n\n
\r\n
" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-ballot-status", + "valueString": "External" + }, + { + "url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-fmm", + "valueInteger": 0 + } + ], + "url": "http://hl7.org/fhir/v3/Ethnicity", + "identifier": { + "system": "urn:ietf:rfc:3986", + "value": "urn:oid:2.16.840.1.113883.5.50" + }, + "version": "2016-11-11", + "name": "v3 Code System Ethnicity", + "status": "active", + "experimental": false, + "date": "2016-11-11T00:00:00+11:00", + "publisher": "HL7, Inc", + "contact": [ + { + "telecom": [ + { + "system": "url", + "value": "http://hl7.org" + } + ] + } + ], + "description": " In the United States, federal standards for classifying data on ethnicity determine the categories used by federal agencies and exert a strong influence on categorization by state and local agencies and private sector organizations. The federal standards do not conceptually define ethnicity, and they recognize the absence of an anthropological or scientific basis for ethnicity classification. Instead, the federal standards acknowledge that ethnicity is a social-political construct in which an individual's own identification with a particular ethnicity is preferred to observer identification. The standards specify two minimum ethnicity categories: Hispanic or Latino, and Not Hispanic or Latino. The standards define a Hispanic or Latino as a person of \"Mexican, Puerto Rican, Cuban, South or Central America, or other Spanish culture or origin, regardless of race.\" The standards stipulate that ethnicity data need not be limited to the two minimum categories, but any expansion must be collapsible to those categories. In addition, the standards stipulate that an individual can be Hispanic or Latino or can be Not Hispanic or Latino, but cannot be both.", + "caseSensitive": true, + "valueSet": "http://hl7.org/fhir/ValueSet/v3-Ethnicity", + "hierarchyMeaning": "is-a", + "content": "complete", + "concept": [ + { + "code": "2135-2", + "display": "Hispanic or Latino", + "definition": "Hispanic or Latino", + "concept": [ + { + "code": "2137-8", + "display": "Spaniard", + "definition": "Spaniard", + "concept": [ + { + "code": "2138-6", + "display": "Andalusian", + "definition": "Andalusian" + }, + { + "code": "2139-4", + "display": "Asturian", + "definition": "Asturian" + }, + { + "code": "2140-2", + "display": "Castillian", + "definition": "Castillian" + }, + { + "code": "2141-0", + "display": "Catalonian", + "definition": "Catalonian" + }, + { + "code": "2142-8", + "display": "Belearic Islander", + "definition": "Belearic Islander" + }, + { + "code": "2143-6", + "display": "Gallego", + "definition": "Gallego" + }, + { + "code": "2144-4", + "display": "Valencian", + "definition": "Valencian" + }, + { + "code": "2145-1", + "display": "Canarian", + "definition": "Canarian" + }, + { + "code": "2146-9", + "display": "Spanish Basque", + "definition": "Spanish Basque" + } + ] + }, + { + "code": "2148-5", + "display": "Mexican", + "definition": "Mexican", + "concept": [ + { + "code": "2149-3", + "display": "Mexican American", + "definition": "Mexican American" + }, + { + "code": "2150-1", + "display": "Mexicano", + "definition": "Mexicano" + }, + { + "code": "2151-9", + "display": "Chicano", + "definition": "Chicano" + }, + { + "code": "2152-7", + "display": "La Raza", + "definition": "La Raza" + }, + { + "code": "2153-5", + "display": "Mexican American Indian", + "definition": "Mexican American Indian" + } + ] + }, + { + "code": "2155-0", + "display": "Central American", + "definition": "Central American", + "concept": [ + { + "code": "2156-8", + "display": "Costa Rican", + "definition": "Costa Rican" + }, + { + "code": "2157-6", + "display": "Guatemalan", + "definition": "Guatemalan" + }, + { + "code": "2158-4", + "display": "Honduran", + "definition": "Honduran" + }, + { + "code": "2159-2", + "display": "Nicaraguan", + "definition": "Nicaraguan" + }, + { + "code": "2160-0", + "display": "Panamanian", + "definition": "Panamanian" + }, + { + "code": "2161-8", + "display": "Salvadoran", + "definition": "Salvadoran" + }, + { + "code": "2162-6", + "display": "Central American Indian", + "definition": "Central American Indian" + }, + { + "code": "2163-4", + "display": "Canal Zone", + "definition": "Canal Zone" + } + ] + }, + { + "code": "2165-9", + "display": "South American", + "definition": "South American", + "concept": [ + { + "code": "2166-7", + "display": "Argentinean", + "definition": "Argentinean" + }, + { + "code": "2167-5", + "display": "Bolivian", + "definition": "Bolivian" + }, + { + "code": "2168-3", + "display": "Chilean", + "definition": "Chilean" + }, + { + "code": "2169-1", + "display": "Colombian", + "definition": "Colombian" + }, + { + "code": "2170-9", + "display": "Ecuadorian", + "definition": "Ecuadorian" + }, + { + "code": "2171-7", + "display": "Paraguayan", + "definition": "Paraguayan" + }, + { + "code": "2172-5", + "display": "Peruvian", + "definition": "Peruvian" + }, + { + "code": "2173-3", + "display": "Uruguayan", + "definition": "Uruguayan" + }, + { + "code": "2174-1", + "display": "Venezuelan", + "definition": "Venezuelan" + }, + { + "code": "2175-8", + "display": "South American Indian", + "definition": "South American Indian" + }, + { + "code": "2176-6", + "display": "Criollo", + "definition": "Criollo" + } + ] + }, + { + "code": "2178-2", + "display": "Latin American", + "definition": "Latin American" + }, + { + "code": "2180-8", + "display": "Puerto Rican", + "definition": "Puerto Rican" + }, + { + "code": "2182-4", + "display": "Cuban", + "definition": "Cuban" + }, + { + "code": "2184-0", + "display": "Dominican", + "definition": "Dominican" + } + ] + }, + { + "code": "2186-5", + "display": "Not Hispanic or Latino", + "definition": "Note that this term remains in the table for completeness, even though within HL7, the notion of \"not otherwise coded\" term is deprecated." + } + ] +} \ No newline at end of file diff --git a/portal/models/code_systems/v3-Race.cs.json b/portal/models/code_systems/v3-Race.cs.json new file mode 100644 index 000000000..9d854f17c --- /dev/null +++ b/portal/models/code_systems/v3-Race.cs.json @@ -0,0 +1,4846 @@ +{ + "resourceType": "CodeSystem", + "id": "v3-Race", + "meta": { + "lastUpdated": "2016-11-11T00:00:00.000+11:00" + }, + "text": { + "status": "generated", + "div": "

Release Date: 2016-11-11

\r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
LevelCodeDisplayDefinition
11002-5 American Indian or Alaska Native
2  1004-1 American Indian
3    1006-6 Abenaki
3    1008-2 Algonquian
3    1010-8 Apache
4      1011-6 Chiricahua
4      1012-4 Fort Sill Apache
4      1013-2 Jicarilla Apache
4      1014-0 Lipan Apache
4      1015-7 Mescalero Apache
4      1016-5 Oklahoma Apache
4      1017-3 Payson Apache
4      1018-1 San Carlos Apache
4      1019-9 White Mountain Apache
3    1021-5 Arapaho
4      1022-3 Northern Arapaho
4      1023-1 Southern Arapaho
4      1024-9 Wind River Arapaho
3    1026-4 Arikara
3    1028-0 Assiniboine
3    1030-6 Assiniboine Sioux
4      1031-4 Fort Peck Assiniboine Sioux
3    1033-0 Bannock
3    1035-5 Blackfeet
3    1037-1 Brotherton
3    1039-7 Burt Lake Band
3    1041-3 Caddo
4      1042-1 Oklahoma Cado
3    1044-7 Cahuilla
4      1045-4 Agua Caliente Cahuilla
4      1046-2 Augustine
4      1047-0 Cabazon
4      1048-8 Los Coyotes
4      1049-6 Morongo
4      1050-4 Santa Rosa Cahuilla
4      1051-2 Torres-Martinez
3    1053-8 California Tribes
4      1054-6 Cahto
4      1055-3 Chimariko
4      1056-1 Coast Miwok
4      1057-9 Digger
4      1058-7 Kawaiisu
4      1059-5 Kern River
4      1060-3 Mattole
4      1061-1 Red Wood
4      1062-9 Santa Rosa
4      1063-7 Takelma
4      1064-5 Wappo
4      1065-2 Yana
4      1066-0 Yuki
3    1068-6 Canadian and Latin American Indian
4      1069-4 Canadian Indian
4      1070-2 Central American Indian
4      1071-0 French American Indian
4      1072-8 Mexican American Indian
4      1073-6 South American Indian
3    1074-4 Spanish American Indian
3    1076-9 Catawba
4      1741-8 Alatna
4      1742-6 Alexander
4      1743-4 Allakaket
4      1744-2 Alanvik
4      1745-9 Anvik
4      1746-7 Arctic
4      1747-5 Beaver
4      1748-3 Birch Creek
4      1749-1 Cantwell
4      1750-9 Chalkyitsik
4      1751-7 Chickaloon
4      1752-5 Chistochina
4      1753-3 Chitina
4      1754-1 Circle
4      1755-8 Cook Inlet
4      1756-6 Copper Center
4      1757-4 Copper River
4      1758-2 Dot Lake
4      1759-0 Doyon
4      1760-8 Eagle
4      1761-6 Eklutna
4      1762-4 Evansville
4      1763-2 Fort Yukon
4      1764-0 Gakona
4      1765-7 Galena
4      1766-5 Grayling
4      1767-3 Gulkana
4      1768-1 Healy Lake
4      1769-9 Holy Cross
4      1770-7 Hughes
4      1771-5 Huslia
4      1772-3 Iliamna
4      1773-1 Kaltag
4      1774-9 Kluti Kaah
4      1775-6 Knik
4      1776-4 Koyukuk
4      1777-2 Lake Minchumina
4      1778-0 Lime
4      1779-8 Mcgrath
4      1780-6 Manley Hot Springs
4      1781-4 Mentasta Lake
4      1782-2 Minto
4      1783-0 Nenana
4      1784-8 Nikolai
4      1785-5 Ninilchik
4      1786-3 Nondalton
4      1787-1 Northway
4      1788-9 Nulato
4      1789-7 Pedro Bay
4      1790-5 Rampart
4      1791-3 Ruby
4      1792-1 Salamatof
4      1793-9 Seldovia
4      1794-7 Slana
4      1795-4 Shageluk
4      1796-2 Stevens
4      1797-0 Stony River
4      1798-8 Takotna
4      1799-6 Tanacross
4      1800-2 Tanaina
4      1801-0 Tanana
4      1802-8 Tanana Chiefs
4      1803-6 Tazlina
4      1804-4 Telida
4      1805-1 Tetlin
4      1806-9 Tok
4      1807-7 Tyonek
4      1808-5 Venetie
4      1809-3 Wiseman
3    1078-5 Cayuse
3    1080-1 Chehalis
3    1082-7 Chemakuan
4      1083-5 Hoh
4      1084-3 Quileute
3    1086-8 Chemehuevi
3    1088-4 Cherokee
4      1089-2 Cherokee Alabama
4      1090-0 Cherokees of Northeast Alabama
4      1091-8 Cherokees of Southeast Alabama
4      1092-6 Eastern Cherokee
4      1093-4 Echota Cherokee
4      1094-2 Etowah Cherokee
4      1095-9 Northern Cherokee
4      1096-7 Tuscola
4      1097-5 United Keetowah Band of Cherokee
4      1098-3 Western Cherokee
3    1100-7 Cherokee Shawnee
3    1102-3 Cheyenne
4      1103-1 Northern Cheyenne
4      1104-9 Southern Cheyenne
3    1106-4 Cheyenne-Arapaho
3    1108-0 Chickahominy
4      1109-8 Eastern Chickahominy
4      1110-6 Western Chickahominy
3    1112-2 Chickasaw
3    1114-8 Chinook
4      1115-5 Clatsop
4      1116-3 Columbia River Chinook
4      1117-1 Kathlamet
4      1118-9 Upper Chinook
4      1119-7 Wakiakum Chinook
4      1120-5 Willapa Chinook
4      1121-3 Wishram
3    1123-9 Chippewa
4      1124-7 Bad River
4      1125-4 Bay Mills Chippewa
4      1126-2 Bois Forte
4      1127-0 Burt Lake Chippewa
4      1128-8 Fond du Lac
4      1129-6 Grand Portage
4      1130-4 Grand Traverse Band of Ottawa-Chippewa
4      1131-2 Keweenaw
4      1132-0 Lac Courte Oreilles
4      1133-8 Lac du Flambeau
4      1134-6 Lac Vieux Desert Chippewa
4      1135-3 Lake Superior
4      1136-1 Leech Lake
4      1137-9 Little Shell Chippewa
4      1138-7 Mille Lacs
4      1139-5 Minnesota Chippewa
4      1140-3 Ontonagon
4      1141-1 Red Cliff Chippewa
4      1142-9 Red Lake Chippewa
4      1143-7 Saginaw Chippewa
4      1144-5 St. Croix Chippewa
4      1145-2 Sault Ste. Marie Chippewa
4      1146-0 Sokoagon Chippewa
4      1147-8 Turtle Mountain
4      1148-6 White Earth
3    1150-2 Chippewa Cree
4      1151-0 Rocky Boy's Chippewa Cree
3    1153-6 Chitimacha
3    1155-1 Choctaw
4      1156-9 Clifton Choctaw
4      1157-7 Jena Choctaw
4      1158-5 Mississippi Choctaw
4      1159-3 Mowa Band of Choctaw
4      1160-1 Oklahoma Choctaw
3    1162-7 Chumash
4      1163-5 Santa Ynez
3    1165-0 Clear Lake
3    1167-6 Coeur D'Alene
3    1169-2 Coharie
3    1171-8 Colorado River
3    1173-4 Colville
3    1175-9 Comanche
4      1176-7 Oklahoma Comanche
3    1178-3 Coos, Lower Umpqua, Siuslaw
3    1180-9 Coos
3    1182-5 Coquilles
3    1184-1 Costanoan
3    1186-6 Coushatta
4      1187-4 Alabama Coushatta
3    1189-0 Cowlitz
3    1191-6 Cree
3    1193-2 Creek
4      1194-0 Alabama Creek
4      1195-7 Alabama Quassarte
4      1196-5 Eastern Creek
4      1197-3 Eastern Muscogee
4      1198-1 Kialegee
4      1199-9 Lower Muscogee
4      1200-5 Machis Lower Creek Indian
4      1201-3 Poarch Band
4      1202-1 Principal Creek Indian Nation
4      1203-9 Star Clan of Muscogee Creeks
4      1204-7 Thlopthlocco
4      1205-4 Tuckabachee
3    1207-0 Croatan
3    1209-6 Crow
3    1211-2 Cupeno
4      1212-0 Agua Caliente
3    1214-6 Delaware
4      1215-3 Eastern Delaware
4      1216-1 Lenni-Lenape
4      1217-9 Munsee
4      1218-7 Oklahoma Delaware
4      1219-5 Rampough Mountain
4      1220-3 Sand Hill
3    1222-9 Diegueno
4      1223-7 Campo
4      1224-5 Capitan Grande
4      1225-2 Cuyapaipe
4      1226-0 La Posta
4      1227-8 Manzanita
4      1228-6 Mesa Grande
4      1229-4 San Pasqual
4      1230-2 Santa Ysabel
4      1231-0 Sycuan
3    1233-6 Eastern Tribes
4      1234-4 Attacapa
4      1235-1 Biloxi
4      1236-9 Georgetown
4      1237-7 Moor
4      1238-5 Nansemond
4      1239-3 Natchez
4      1240-1 Nausu Waiwash
4      1241-9 Nipmuc
4      1242-7 Paugussett
4      1243-5 Pocomoke Acohonock
4      1244-3 Southeastern Indians
4      1245-0 Susquehanock
4      1246-8 Tunica Biloxi
4      1247-6 Waccamaw-Siousan
4      1248-4 Wicomico
3    1250-0 Esselen
3    1252-6 Fort Belknap
3    1254-2 Fort Berthold
3    1256-7 Fort Mcdowell
3    1258-3 Fort Hall
3    1260-9 Gabrieleno
3    1262-5 Grand Ronde
3    1264-1 Gros Ventres
4      1265-8 Atsina
3    1267-4 Haliwa
3    1269-0 Hidatsa
3    1271-6 Hoopa
4      1272-4 Trinity
4      1273-2 Whilkut
3    1275-7 Hoopa Extension
3    1277-3 Houma
3    1279-9 Inaja-Cosmit
3    1281-5 Iowa
4      1282-3 Iowa of Kansas-Nebraska
4      1283-1 Iowa of Oklahoma
3    1285-6 Iroquois
4      1286-4 Cayuga
4      1287-2 Mohawk
4      1288-0 Oneida
4      1289-8 Onondaga
4      1290-6 Seneca
4      1291-4 Seneca Nation
4      1292-2 Seneca-Cayuga
4      1293-0 Tonawanda Seneca
4      1294-8 Tuscarora
4      1295-5 Wyandotte
3    1297-1 Juaneno
3    1299-7 Kalispel
3    1301-1 Karuk
3    1303-7 Kaw
3    1305-2 Kickapoo
4      1306-0 Oklahoma Kickapoo
4      1307-8 Texas Kickapoo
3    1309-4 Kiowa
4      1310-2 Oklahoma Kiowa
3    1312-8 Klallam
4      1313-6 Jamestown
4      1314-4 Lower Elwha
4      1315-1 Port Gamble Klallam
3    1317-7 Klamath
3    1319-3 Konkow
3    1321-9 Kootenai
3    1323-5 Lassik
3    1325-0 Long Island
4      1326-8 Matinecock
4      1327-6 Montauk
4      1328-4 Poospatuck
4      1329-2 Setauket
3    1331-8 Luiseno
4      1332-6 La Jolla
4      1333-4 Pala
4      1334-2 Pauma
4      1335-9 Pechanga
4      1336-7 Soboba
4      1337-5 Twenty-Nine Palms
4      1338-3 Temecula
3    1340-9 Lumbee
3    1342-5 Lummi
3    1344-1 Maidu
4      1345-8 Mountain Maidu
4      1346-6 Nishinam
3    1348-2 Makah
3    1350-8 Maliseet
3    1352-4 Mandan
3    1354-0 Mattaponi
3    1356-5 Menominee
3    1358-1 Miami
4      1359-9 Illinois Miami
4      1360-7 Indiana Miami
4      1361-5 Oklahoma Miami
3    1363-1 Miccosukee
3    1365-6 Micmac
4      1366-4 Aroostook
3    1368-0 Mission Indians
3    1370-6 Miwok
3    1372-2 Modoc
3    1374-8 Mohegan
3    1376-3 Mono
3    1378-9 Nanticoke
3    1380-5 Narragansett
3    1382-1 Navajo
4      1383-9 Alamo Navajo
4      1384-7 Canoncito Navajo
4      1385-4 Ramah Navajo
3    1387-0 Nez Perce
3    1389-6 Nomalaki
3    1391-2 Northwest Tribes
4      1392-0 Alsea
4      1393-8 Celilo
4      1394-6 Columbia
4      1395-3 Kalapuya
4      1396-1 Molala
4      1397-9 Talakamish
4      1398-7 Tenino
4      1399-5 Tillamook
4      1400-1 Wenatchee
4      1401-9 Yahooskin
3    1403-5 Omaha
3    1405-0 Oregon Athabaskan
3    1407-6 Osage
3    1409-2 Otoe-Missouria
3    1411-8 Ottawa
4      1412-6 Burt Lake Ottawa
4      1413-4 Michigan Ottawa
4      1414-2 Oklahoma Ottawa
3    1416-7 Paiute
4      1417-5 Bishop
4      1418-3 Bridgeport
4      1419-1 Burns Paiute
4      1420-9 Cedarville
4      1421-7 Fort Bidwell
4      1422-5 Fort Independence
4      1423-3 Kaibab
4      1424-1 Las Vegas
4      1425-8 Lone Pine
4      1426-6 Lovelock
4      1427-4 Malheur Paiute
4      1428-2 Moapa
4      1429-0 Northern Paiute
4      1430-8 Owens Valley
4      1431-6 Pyramid Lake
4      1432-4 San Juan Southern Paiute
4      1433-2 Southern Paiute
4      1434-0 Summit Lake
4      1435-7 Utu Utu Gwaitu Paiute
4      1436-5 Walker River
4      1437-3 Yerington Paiute
3    1439-9 Pamunkey
3    1441-5 Passamaquoddy
4      1442-3 Indian Township
4      1443-1 Pleasant Point Passamaquoddy
3    1445-6 Pawnee
4      1446-4 Oklahoma Pawnee
3    1448-0 Penobscot
3    1450-6 Peoria
4      1451-4 Oklahoma Peoria
3    1453-0 Pequot
4      1454-8 Marshantucket Pequot
3    1456-3 Pima
4      1457-1 Gila River Pima-Maricopa
4      1458-9 Salt River Pima-Maricopa
3    1460-5 Piscataway
3    1462-1 Pit River
3    1464-7 Pomo
4      1465-4 Central Pomo
4      1466-2 Dry Creek
4      1467-0 Eastern Pomo
4      1468-8 Kashia
4      1469-6 Northern Pomo
4      1470-4 Scotts Valley
4      1471-2 Stonyford
4      1472-0 Sulphur Bank
3    1474-6 Ponca
4      1475-3 Nebraska Ponca
4      1476-1 Oklahoma Ponca
3    1478-7 Potawatomi
4      1479-5 Citizen Band Potawatomi
4      1480-3 Forest County
4      1481-1 Hannahville
4      1482-9 Huron Potawatomi
4      1483-7 Pokagon Potawatomi
4      1484-5 Prairie Band
4      1485-2 Wisconsin Potawatomi
3    1487-8 Powhatan
3    1489-4 Pueblo
4      1490-2 Acoma
4      1491-0 Arizona Tewa
4      1492-8 Cochiti
4      1493-6 Hopi
4      1494-4 Isleta
4      1495-1 Jemez
4      1496-9 Keres
4      1497-7 Laguna
4      1498-5 Nambe
4      1499-3 Picuris
4      1500-8 Piro
4      1501-6 Pojoaque
4      1502-4 San Felipe
4      1503-2 San Ildefonso
4      1504-0 San Juan Pueblo
4      1505-7 San Juan De
4      1506-5 San Juan
4      1507-3 Sandia
4      1508-1 Santa Ana
4      1509-9 Santa Clara
4      1510-7 Santo Domingo
4      1511-5 Taos
4      1512-3 Tesuque
4      1513-1 Tewa
4      1514-9 Tigua
4      1515-6 Zia
4      1516-4 Zuni
3    1518-0 Puget Sound Salish
4      1519-8 Duwamish
4      1520-6 Kikiallus
4      1521-4 Lower Skagit
4      1522-2 Muckleshoot
4      1523-0 Nisqually
4      1524-8 Nooksack
4      1525-5 Port Madison
4      1526-3 Puyallup
4      1527-1 Samish
4      1528-9 Sauk-Suiattle
4      1529-7 Skokomish
4      1530-5 Skykomish
4      1531-3 Snohomish
4      1532-1 Snoqualmie
4      1533-9 Squaxin Island
4      1534-7 Steilacoom
4      1535-4 Stillaguamish
4      1536-2 Suquamish
4      1537-0 Swinomish
4      1538-8 Tulalip
4      1539-6 Upper Skagit
3    1541-2 Quapaw
3    1543-8 Quinault
3    1545-3 Rappahannock
3    1547-9 Reno-Sparks
3    1549-5 Round Valley
3    1551-1 Sac and Fox
4      1552-9 Iowa Sac and Fox
4      1553-7 Missouri Sac and Fox
4      1554-5 Oklahoma Sac and Fox
3    1556-0 Salinan
3    1558-6 Salish
3    1560-2 Salish and Kootenai
3    1562-8 Schaghticoke
3    1564-4 Scott Valley
3    1566-9 Seminole
4      1567-7 Big Cypress
4      1568-5 Brighton
4      1569-3 Florida Seminole
4      1570-1 Hollywood Seminole
4      1571-9 Oklahoma Seminole
3    1573-5 Serrano
4      1574-3 San Manual
3    1576-8 Shasta
3    1578-4 Shawnee
4      1579-2 Absentee Shawnee
4      1580-0 Eastern Shawnee
3    1582-6 Shinnecock
3    1584-2 Shoalwater Bay
3    1586-7 Shoshone
4      1587-5 Battle Mountain
4      1588-3 Duckwater
4      1589-1 Elko
4      1590-9 Ely
4      1591-7 Goshute
4      1592-5 Panamint
4      1593-3 Ruby Valley
4      1594-1 Skull Valley
4      1595-8 South Fork Shoshone
4      1596-6 Te-Moak Western Shoshone
4      1597-4 Timbi-Sha Shoshone
4      1598-2 Washakie
4      1599-0 Wind River Shoshone
4      1600-6 Yomba
3    1602-2 Shoshone Paiute
4      1603-0 Duck Valley
4      1604-8 Fallon
4      1605-5 Fort McDermitt
3    1607-1 Siletz
3    1609-7 Sioux
4      1610-5 Blackfoot Sioux
4      1611-3 Brule Sioux
4      1612-1 Cheyenne River Sioux
4      1613-9 Crow Creek Sioux
4      1614-7 Dakota Sioux
4      1615-4 Flandreau Santee
4      1616-2 Fort Peck
4      1617-0 Lake Traverse Sioux
4      1618-8 Lower Brule Sioux
4      1619-6 Lower Sioux
4      1620-4 Mdewakanton Sioux
4      1621-2 Miniconjou
4      1622-0 Oglala Sioux
4      1623-8 Pine Ridge Sioux
4      1624-6 Pipestone Sioux
4      1625-3 Prairie Island Sioux
4      1626-1 Prior Lake Sioux
4      1627-9 Rosebud Sioux
4      1628-7 Sans Arc Sioux
4      1629-5 Santee Sioux
4      1630-3 Sisseton-Wahpeton
4      1631-1 Sisseton Sioux
4      1632-9 Spirit Lake Sioux
4      1633-7 Standing Rock Sioux
4      1634-5 Teton Sioux
4      1635-2 Two Kettle Sioux
4      1636-0 Upper Sioux
4      1637-8 Wahpekute Sioux
4      1638-6 Wahpeton Sioux
4      1639-4 Wazhaza Sioux
4      1640-2 Yankton Sioux
4      1641-0 Yanktonai Sioux
3    1643-6 Siuslaw
3    1645-1 Spokane
3    1647-7 Stewart
3    1649-3 Stockbridge
3    1651-9 Susanville
3    1653-5 Tohono O'Odham
4      1654-3 Ak-Chin
4      1655-0 Gila Bend
4      1656-8 San Xavier
4      1657-6 Sells
3    1659-2 Tolowa
3    1661-8 Tonkawa
3    1663-4 Tygh
3    1665-9 Umatilla
3    1667-5 Umpqua
4      1668-3 Cow Creek Umpqua
3    1670-9 Ute
4      1671-7 Allen Canyon
4      1672-5 Uintah Ute
4      1673-3 Ute Mountain Ute
3    1675-8 Wailaki
3    1677-4 Walla-Walla
3    1679-0 Wampanoag
4      1680-8 Gay Head Wampanoag
4      1681-6 Mashpee Wampanoag
3    1683-2 Warm Springs
3    1685-7 Wascopum
3    1687-3 Washoe
4      1688-1 Alpine
4      1689-9 Carson
4      1690-7 Dresslerville
3    1692-3 Wichita
3    1694-9 Wind River
3    1696-4 Winnebago
4      1697-2 Ho-chunk
4      1698-0 Nebraska Winnebago
3    1700-4 Winnemucca
3    1702-0 Wintun
3    1704-6 Wiyot
4      1705-3 Table Bluff
3    1707-9 Yakama
3    1709-5 Yakama Cowlitz
3    1711-1 Yaqui
4      1712-9 Barrio Libre
4      1713-7 Pascua Yaqui
3    1715-2 Yavapai Apache
3    1717-8 Yokuts
4      1718-6 Chukchansi
4      1719-4 Tachi
4      1720-2 Tule River
3    1722-8 Yuchi
3    1724-4 Yuman
4      1725-1 Cocopah
4      1726-9 Havasupai
4      1727-7 Hualapai
4      1728-5 Maricopa
4      1729-3 Mohave
4      1730-1 Quechan
4      1731-9 Yavapai
3    1732-7 Yurok
4      1733-5 Coast Yurok
2  1735-0 Alaska Native
3    1737-6 Alaska Indian
4      1739-2 Alaskan Athabascan
5        1740-0 Ahtna
4      1811-9 Southeast Alaska
5        1813-5 Tlingit-Haida
6          1814-3 Angoon
6          1815-0 Central Council of Tlingit and Haida Tribes
6          1816-8 Chilkat
6          1817-6 Chilkoot
6          1818-4 Craig
6          1819-2 Douglas
6          1820-0 Haida
6          1821-8 Hoonah
6          1822-6 Hydaburg
6          1823-4 Kake
6          1824-2 Kasaan
6          1825-9 Kenaitze
6          1826-7 Ketchikan
6          1827-5 Klawock
6          1828-3 Pelican
6          1829-1 Petersburg
6          1830-9 Saxman
6          1831-7 Sitka
6          1832-5 Tenakee Springs
6          1833-3 Tlingit
6          1834-1 Wrangell
6          1835-8 Yakutat
5        1837-4 Tsimshian
6          1838-2 Metlakatla
3    1840-8 Eskimo
4      1842-4 Greenland Eskimo
4      1844-0 Inupiat Eskimo
5        1845-7 Ambler
5        1846-5 Anaktuvuk
5        1847-3 Anaktuvuk Pass
5        1848-1 Arctic Slope Inupiat
5        1849-9 Arctic Slope Corporation
5        1850-7 Atqasuk
5        1851-5 Barrow
5        1852-3 Bering Straits Inupiat
5        1853-1 Brevig Mission
5        1854-9 Buckland
5        1855-6 Chinik
5        1856-4 Council
5        1857-2 Deering
5        1858-0 Elim
5        1859-8 Golovin
5        1860-6 Inalik Diomede
5        1861-4 Inupiaq
5        1862-2 Kaktovik
5        1863-0 Kawerak
5        1864-8 Kiana
5        1865-5 Kivalina
5        1866-3 Kobuk
5        1867-1 Kotzebue
5        1868-9 Koyuk
5        1869-7 Kwiguk
5        1870-5 Mauneluk Inupiat
5        1871-3 Nana Inupiat
5        1872-1 Noatak
5        1873-9 Nome
5        1874-7 Noorvik
5        1875-4 Nuiqsut
5        1876-2 Point Hope
5        1877-0 Point Lay
5        1878-8 Selawik
5        1879-6 Shaktoolik
5        1880-4 Shishmaref
5        1881-2 Shungnak
5        1882-0 Solomon
5        1883-8 Teller
5        1884-6 Unalakleet
5        1885-3 Wainwright
5        1886-1 Wales
5        1887-9 White Mountain
5        1888-7 White Mountain Inupiat
5        1889-5 Mary's Igloo
4      1891-1 Siberian Eskimo
5        1892-9 Gambell
5        1893-7 Savoonga
5        1894-5 Siberian Yupik
4      1896-0 Yupik Eskimo
5        1897-8 Akiachak
5        1898-6 Akiak
5        1899-4 Alakanuk
5        1900-0 Aleknagik
5        1901-8 Andreafsky
5        1902-6 Aniak
5        1903-4 Atmautluak
5        1904-2 Bethel
5        1905-9 Bill Moore's Slough
5        1906-7 Bristol Bay Yupik
5        1907-5 Calista Yupik
5        1908-3 Chefornak
5        1909-1 Chevak
5        1910-9 Chuathbaluk
5        1911-7 Clark's Point
5        1912-5 Crooked Creek
5        1913-3 Dillingham
5        1914-1 Eek
5        1915-8 Ekuk
5        1916-6 Ekwok
5        1917-4 Emmonak
5        1918-2 Goodnews Bay
5        1919-0 Hooper Bay
5        1920-8 Iqurmuit (Russian Mission)
5        1921-6 Kalskag
5        1922-4 Kasigluk
5        1923-2 Kipnuk
5        1924-0 Koliganek
5        1925-7 Kongiganak
5        1926-5 Kotlik
5        1927-3 Kwethluk
5        1928-1 Kwigillingok
5        1929-9 Levelock
5        1930-7 Lower Kalskag
5        1931-5 Manokotak
5        1932-3 Marshall
5        1933-1 Mekoryuk
5        1934-9 Mountain Village
5        1935-6 Naknek
5        1936-4 Napaumute
5        1937-2 Napakiak
5        1938-0 Napaskiak
5        1939-8 Newhalen
5        1940-6 New Stuyahok
5        1941-4 Newtok
5        1942-2 Nightmute
5        1943-0 Nunapitchukv
5        1944-8 Oscarville
5        1945-5 Pilot Station
5        1946-3 Pitkas Point
5        1947-1 Platinum
5        1948-9 Portage Creek
5        1949-7 Quinhagak
5        1950-5 Red Devil
5        1951-3 St. Michael
5        1952-1 Scammon Bay
5        1953-9 Sheldon's Point
5        1954-7 Sleetmute
5        1955-4 Stebbins
5        1956-2 Togiak
5        1957-0 Toksook
5        1958-8 Tulukskak
5        1959-6 Tuntutuliak
5        1960-4 Tununak
5        1961-2 Twin Hills
5        1962-0 Georgetown
5        1963-8 St. Mary's
5        1964-6 Umkumiate
3    1966-1 Aleut
4      1968-7 Alutiiq Aleut
5        1969-5 Tatitlek
5        1970-3 Ugashik
4      1972-9 Bristol Bay Aleut
5        1973-7 Chignik
5        1974-5 Chignik Lake
5        1975-2 Egegik
5        1976-0 Igiugig
5        1977-8 Ivanof Bay
5        1978-6 King Salmon
5        1979-4 Kokhanok
5        1980-2 Perryville
5        1981-0 Pilot Point
5        1982-8 Port Heiden
4      1984-4 Chugach Aleut
5        1985-1 Chenega
5        1986-9 Chugach Corporation
5        1987-7 English Bay
5        1988-5 Port Graham
4      1990-1 Eyak
4      1992-7 Koniag Aleut
5        1993-5 Akhiok
5        1994-3 Agdaagux
5        1995-0 Karluk
5        1996-8 Kodiak
5        1997-6 Larsen Bay
5        1998-4 Old Harbor
5        1999-2 Ouzinkie
5        2000-8 Port Lions
4      2002-4 Sugpiaq
4      2004-0 Suqpigaq
4      2006-5 Unangan Aleut
5        2007-3 Akutan
5        2008-1 Aleut Corporation
5        2009-9 Aleutian
5        2010-7 Aleutian Islander
5        2011-5 Atka
5        2012-3 Belkofski
5        2013-1 Chignik Lagoon
5        2014-9 King Cove
5        2015-6 False Pass
5        2016-4 Nelson Lagoon
5        2017-2 Nikolski
5        2018-0 Pauloff Harbor
5        2019-8 Qagan Toyagungin
5        2020-6 Qawalangin
5        2021-4 St. George
5        2022-2 St. Paul
5        2023-0 Sand Point
5        2024-8 South Naknek
5        2025-5 Unalaska
5        2026-3 Unga
12028-9 Asian
2  2029-7 Asian Indian
2  2030-5 Bangladeshi
2  2031-3 Bhutanese
2  2032-1 Burmese
2  2033-9 Cambodian
2  2034-7 Chinese
2  2035-4 Taiwanese
2  2036-2 Filipino
2  2037-0 Hmong
2  2038-8 Indonesian
2  2039-6 Japanese
2  2040-4 Korean
2  2041-2 Laotian
2  2042-0 Malaysian
2  2043-8 Okinawan
2  2044-6 Pakistani
2  2045-3 Sri Lankan
2  2046-1 Thai
2  2047-9 Vietnamese
2  2048-7 Iwo Jiman
2  2049-5 Maldivian
2  2050-3 Nepalese
2  2051-1 Singaporean
2  2052-9 Madagascar
12054-5 Black or African American
2  2056-0 Black
2  2058-6 African American
2  2060-2 African
3    2061-0 Botswanan
3    2062-8 Ethiopian
3    2063-6 Liberian
3    2064-4 Namibian
3    2065-1 Nigerian
3    2066-9 Zairean
2  2067-7 Bahamian
2  2068-5 Barbadian
2  2069-3 Dominican
2  2070-1 Dominica Islander
2  2071-9 Haitian
2  2072-7 Jamaican
2  2073-5 Tobagoan
2  2074-3 Trinidadian
2  2075-0 West Indian
12076-8 Native Hawaiian or Other Pacific Islander
2  2078-4 Polynesian
3    2079-2 Native Hawaiian
3    2080-0 Samoan
3    2081-8 Tahitian
3    2082-6 Tongan
3    2083-4 Tokelauan
2  2085-9 Micronesian
3    2086-7 Guamanian or Chamorro
3    2087-5 Guamanian
3    2088-3 Chamorro
3    2089-1 Mariana Islander
3    2090-9 Marshallese
3    2091-7 Palauan
3    2092-5 Carolinian
3    2093-3 Kosraean
3    2094-1 Pohnpeian
3    2095-8 Saipanese
3    2096-6 Kiribati
3    2097-4 Chuukese
3    2098-2 Yapese
2  2100-6 Melanesian
3    2101-4 Fijian
3    2102-2 Papua New Guinean
3    2103-0 Solomon Islander
3    2104-8 New Hebrides
2  2500-7 Other Pacific Islander\n Note that this term remains in the table for completeness, even though within HL7, the notion of Other code is deprecated.
\r\n\n
12106-3 White
2  2108-9 European
3    2109-7 Armenian
3    2110-5 English
3    2111-3 French
3    2112-1 German
3    2113-9 Irish
3    2114-7 Italian
3    2115-4 Polish
3    2116-2 Scottish
2  2118-8 Middle Eastern or North African
3    2119-6 Assyrian
3    2120-4 Egyptian
3    2121-2 Iranian
3    2122-0 Iraqi
3    2123-8 Lebanese
3    2124-6 Palestinian
3    2125-3 Syrian
3    2126-1 Afghanistani
3    2127-9 Israeili
2  2129-5 Arab
12131-1 Other Race\n Note that this term remains in the table for completeness, even though within HL7, the notion of Other code is deprecated.
\r\n\n
\r\n
" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-ballot-status", + "valueString": "External" + }, + { + "url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-fmm", + "valueInteger": 0 + } + ], + "url": "http://hl7.org/fhir/v3/Race", + "identifier": { + "system": "urn:ietf:rfc:3986", + "value": "urn:oid:2.16.840.1.113883.5.104" + }, + "version": "2016-11-11", + "name": "v3 Code System Race", + "status": "active", + "experimental": false, + "date": "2016-11-11T00:00:00+11:00", + "publisher": "HL7, Inc", + "contact": [ + { + "telecom": [ + { + "system": "url", + "value": "http://hl7.org" + } + ] + } + ], + "description": " In the United States, federal standards for classifying data on race determine the categories used by federal agencies and exert a strong influence on categorization by state and local agencies and private sector organizations. The federal standards do not conceptually define race, and they recognize the absence of an anthropological or scientific basis for racial classification. Instead, the federal standards acknowledge that race is a social-political construct in which an individual's own identification with one more race categories is preferred to observer identification. The standards use a variety of features to define five minimum race categories. Among these features are descent from \"the original peoples\" of a specified region or nation. The minimum race categories are American Indian or Alaska Native, Asian, Black or African American, Native Hawaiian or Other Pacific Islander, and White. The federal standards stipulate that race data need not be limited to the five minimum categories, but any expansion must be collapsible to those categories.", + "caseSensitive": true, + "valueSet": "http://hl7.org/fhir/ValueSet/v3-Race", + "hierarchyMeaning": "is-a", + "content": "complete", + "concept": [ + { + "code": "1002-5", + "display": "American Indian or Alaska Native", + "definition": "American Indian or Alaska Native", + "concept": [ + { + "code": "1004-1", + "display": "American Indian", + "definition": "American Indian", + "concept": [ + { + "code": "1006-6", + "display": "Abenaki", + "definition": "Abenaki" + }, + { + "code": "1008-2", + "display": "Algonquian", + "definition": "Algonquian" + }, + { + "code": "1010-8", + "display": "Apache", + "definition": "Apache", + "concept": [ + { + "code": "1011-6", + "display": "Chiricahua", + "definition": "Chiricahua" + }, + { + "code": "1012-4", + "display": "Fort Sill Apache", + "definition": "Fort Sill Apache" + }, + { + "code": "1013-2", + "display": "Jicarilla Apache", + "definition": "Jicarilla Apache" + }, + { + "code": "1014-0", + "display": "Lipan Apache", + "definition": "Lipan Apache" + }, + { + "code": "1015-7", + "display": "Mescalero Apache", + "definition": "Mescalero Apache" + }, + { + "code": "1016-5", + "display": "Oklahoma Apache", + "definition": "Oklahoma Apache" + }, + { + "code": "1017-3", + "display": "Payson Apache", + "definition": "Payson Apache" + }, + { + "code": "1018-1", + "display": "San Carlos Apache", + "definition": "San Carlos Apache" + }, + { + "code": "1019-9", + "display": "White Mountain Apache", + "definition": "White Mountain Apache" + } + ] + }, + { + "code": "1021-5", + "display": "Arapaho", + "definition": "Arapaho", + "concept": [ + { + "code": "1022-3", + "display": "Northern Arapaho", + "definition": "Northern Arapaho" + }, + { + "code": "1023-1", + "display": "Southern Arapaho", + "definition": "Southern Arapaho" + }, + { + "code": "1024-9", + "display": "Wind River Arapaho", + "definition": "Wind River Arapaho" + } + ] + }, + { + "code": "1026-4", + "display": "Arikara", + "definition": "Arikara" + }, + { + "code": "1028-0", + "display": "Assiniboine", + "definition": "Assiniboine" + }, + { + "code": "1030-6", + "display": "Assiniboine Sioux", + "definition": "Assiniboine Sioux", + "concept": [ + { + "code": "1031-4", + "display": "Fort Peck Assiniboine Sioux", + "definition": "Fort Peck Assiniboine Sioux" + } + ] + }, + { + "code": "1033-0", + "display": "Bannock", + "definition": "Bannock" + }, + { + "code": "1035-5", + "display": "Blackfeet", + "definition": "Blackfeet" + }, + { + "code": "1037-1", + "display": "Brotherton", + "definition": "Brotherton" + }, + { + "code": "1039-7", + "display": "Burt Lake Band", + "definition": "Burt Lake Band" + }, + { + "code": "1041-3", + "display": "Caddo", + "definition": "Caddo", + "concept": [ + { + "code": "1042-1", + "display": "Oklahoma Cado", + "definition": "Oklahoma Cado" + } + ] + }, + { + "code": "1044-7", + "display": "Cahuilla", + "definition": "Cahuilla", + "concept": [ + { + "code": "1045-4", + "display": "Agua Caliente Cahuilla", + "definition": "Agua Caliente Cahuilla" + }, + { + "code": "1046-2", + "display": "Augustine", + "definition": "Augustine" + }, + { + "code": "1047-0", + "display": "Cabazon", + "definition": "Cabazon" + }, + { + "code": "1048-8", + "display": "Los Coyotes", + "definition": "Los Coyotes" + }, + { + "code": "1049-6", + "display": "Morongo", + "definition": "Morongo" + }, + { + "code": "1050-4", + "display": "Santa Rosa Cahuilla", + "definition": "Santa Rosa Cahuilla" + }, + { + "code": "1051-2", + "display": "Torres-Martinez", + "definition": "Torres-Martinez" + } + ] + }, + { + "code": "1053-8", + "display": "California Tribes", + "definition": "California Tribes", + "concept": [ + { + "code": "1054-6", + "display": "Cahto", + "definition": "Cahto" + }, + { + "code": "1055-3", + "display": "Chimariko", + "definition": "Chimariko" + }, + { + "code": "1056-1", + "display": "Coast Miwok", + "definition": "Coast Miwok" + }, + { + "code": "1057-9", + "display": "Digger", + "definition": "Digger" + }, + { + "code": "1058-7", + "display": "Kawaiisu", + "definition": "Kawaiisu" + }, + { + "code": "1059-5", + "display": "Kern River", + "definition": "Kern River" + }, + { + "code": "1060-3", + "display": "Mattole", + "definition": "Mattole" + }, + { + "code": "1061-1", + "display": "Red Wood", + "definition": "Red Wood" + }, + { + "code": "1062-9", + "display": "Santa Rosa", + "definition": "Santa Rosa" + }, + { + "code": "1063-7", + "display": "Takelma", + "definition": "Takelma" + }, + { + "code": "1064-5", + "display": "Wappo", + "definition": "Wappo" + }, + { + "code": "1065-2", + "display": "Yana", + "definition": "Yana" + }, + { + "code": "1066-0", + "display": "Yuki", + "definition": "Yuki" + } + ] + }, + { + "code": "1068-6", + "display": "Canadian and Latin American Indian", + "definition": "Canadian and Latin American Indian", + "concept": [ + { + "code": "1069-4", + "display": "Canadian Indian", + "definition": "Canadian Indian" + }, + { + "code": "1070-2", + "display": "Central American Indian", + "definition": "Central American Indian" + }, + { + "code": "1071-0", + "display": "French American Indian", + "definition": "French American Indian" + }, + { + "code": "1072-8", + "display": "Mexican American Indian", + "definition": "Mexican American Indian" + }, + { + "code": "1073-6", + "display": "South American Indian", + "definition": "South American Indian" + } + ] + }, + { + "code": "1074-4", + "display": "Spanish American Indian", + "definition": "Spanish American Indian" + }, + { + "code": "1076-9", + "display": "Catawba", + "definition": "Catawba", + "concept": [ + { + "code": "1741-8", + "display": "Alatna", + "definition": "Alatna" + }, + { + "code": "1742-6", + "display": "Alexander", + "definition": "Alexander" + }, + { + "code": "1743-4", + "display": "Allakaket", + "definition": "Allakaket" + }, + { + "code": "1744-2", + "display": "Alanvik", + "definition": "Alanvik" + }, + { + "code": "1745-9", + "display": "Anvik", + "definition": "Anvik" + }, + { + "code": "1746-7", + "display": "Arctic", + "definition": "Arctic" + }, + { + "code": "1747-5", + "display": "Beaver", + "definition": "Beaver" + }, + { + "code": "1748-3", + "display": "Birch Creek", + "definition": "Birch Creek" + }, + { + "code": "1749-1", + "display": "Cantwell", + "definition": "Cantwell" + }, + { + "code": "1750-9", + "display": "Chalkyitsik", + "definition": "Chalkyitsik" + }, + { + "code": "1751-7", + "display": "Chickaloon", + "definition": "Chickaloon" + }, + { + "code": "1752-5", + "display": "Chistochina", + "definition": "Chistochina" + }, + { + "code": "1753-3", + "display": "Chitina", + "definition": "Chitina" + }, + { + "code": "1754-1", + "display": "Circle", + "definition": "Circle" + }, + { + "code": "1755-8", + "display": "Cook Inlet", + "definition": "Cook Inlet" + }, + { + "code": "1756-6", + "display": "Copper Center", + "definition": "Copper Center" + }, + { + "code": "1757-4", + "display": "Copper River", + "definition": "Copper River" + }, + { + "code": "1758-2", + "display": "Dot Lake", + "definition": "Dot Lake" + }, + { + "code": "1759-0", + "display": "Doyon", + "definition": "Doyon" + }, + { + "code": "1760-8", + "display": "Eagle", + "definition": "Eagle" + }, + { + "code": "1761-6", + "display": "Eklutna", + "definition": "Eklutna" + }, + { + "code": "1762-4", + "display": "Evansville", + "definition": "Evansville" + }, + { + "code": "1763-2", + "display": "Fort Yukon", + "definition": "Fort Yukon" + }, + { + "code": "1764-0", + "display": "Gakona", + "definition": "Gakona" + }, + { + "code": "1765-7", + "display": "Galena", + "definition": "Galena" + }, + { + "code": "1766-5", + "display": "Grayling", + "definition": "Grayling" + }, + { + "code": "1767-3", + "display": "Gulkana", + "definition": "Gulkana" + }, + { + "code": "1768-1", + "display": "Healy Lake", + "definition": "Healy Lake" + }, + { + "code": "1769-9", + "display": "Holy Cross", + "definition": "Holy Cross" + }, + { + "code": "1770-7", + "display": "Hughes", + "definition": "Hughes" + }, + { + "code": "1771-5", + "display": "Huslia", + "definition": "Huslia" + }, + { + "code": "1772-3", + "display": "Iliamna", + "definition": "Iliamna" + }, + { + "code": "1773-1", + "display": "Kaltag", + "definition": "Kaltag" + }, + { + "code": "1774-9", + "display": "Kluti Kaah", + "definition": "Kluti Kaah" + }, + { + "code": "1775-6", + "display": "Knik", + "definition": "Knik" + }, + { + "code": "1776-4", + "display": "Koyukuk", + "definition": "Koyukuk" + }, + { + "code": "1777-2", + "display": "Lake Minchumina", + "definition": "Lake Minchumina" + }, + { + "code": "1778-0", + "display": "Lime", + "definition": "Lime" + }, + { + "code": "1779-8", + "display": "Mcgrath", + "definition": "Mcgrath" + }, + { + "code": "1780-6", + "display": "Manley Hot Springs", + "definition": "Manley Hot Springs" + }, + { + "code": "1781-4", + "display": "Mentasta Lake", + "definition": "Mentasta Lake" + }, + { + "code": "1782-2", + "display": "Minto", + "definition": "Minto" + }, + { + "code": "1783-0", + "display": "Nenana", + "definition": "Nenana" + }, + { + "code": "1784-8", + "display": "Nikolai", + "definition": "Nikolai" + }, + { + "code": "1785-5", + "display": "Ninilchik", + "definition": "Ninilchik" + }, + { + "code": "1786-3", + "display": "Nondalton", + "definition": "Nondalton" + }, + { + "code": "1787-1", + "display": "Northway", + "definition": "Northway" + }, + { + "code": "1788-9", + "display": "Nulato", + "definition": "Nulato" + }, + { + "code": "1789-7", + "display": "Pedro Bay", + "definition": "Pedro Bay" + }, + { + "code": "1790-5", + "display": "Rampart", + "definition": "Rampart" + }, + { + "code": "1791-3", + "display": "Ruby", + "definition": "Ruby" + }, + { + "code": "1792-1", + "display": "Salamatof", + "definition": "Salamatof" + }, + { + "code": "1793-9", + "display": "Seldovia", + "definition": "Seldovia" + }, + { + "code": "1794-7", + "display": "Slana", + "definition": "Slana" + }, + { + "code": "1795-4", + "display": "Shageluk", + "definition": "Shageluk" + }, + { + "code": "1796-2", + "display": "Stevens", + "definition": "Stevens" + }, + { + "code": "1797-0", + "display": "Stony River", + "definition": "Stony River" + }, + { + "code": "1798-8", + "display": "Takotna", + "definition": "Takotna" + }, + { + "code": "1799-6", + "display": "Tanacross", + "definition": "Tanacross" + }, + { + "code": "1800-2", + "display": "Tanaina", + "definition": "Tanaina" + }, + { + "code": "1801-0", + "display": "Tanana", + "definition": "Tanana" + }, + { + "code": "1802-8", + "display": "Tanana Chiefs", + "definition": "Tanana Chiefs" + }, + { + "code": "1803-6", + "display": "Tazlina", + "definition": "Tazlina" + }, + { + "code": "1804-4", + "display": "Telida", + "definition": "Telida" + }, + { + "code": "1805-1", + "display": "Tetlin", + "definition": "Tetlin" + }, + { + "code": "1806-9", + "display": "Tok", + "definition": "Tok" + }, + { + "code": "1807-7", + "display": "Tyonek", + "definition": "Tyonek" + }, + { + "code": "1808-5", + "display": "Venetie", + "definition": "Venetie" + }, + { + "code": "1809-3", + "display": "Wiseman", + "definition": "Wiseman" + } + ] + }, + { + "code": "1078-5", + "display": "Cayuse", + "definition": "Cayuse" + }, + { + "code": "1080-1", + "display": "Chehalis", + "definition": "Chehalis" + }, + { + "code": "1082-7", + "display": "Chemakuan", + "definition": "Chemakuan", + "concept": [ + { + "code": "1083-5", + "display": "Hoh", + "definition": "Hoh" + }, + { + "code": "1084-3", + "display": "Quileute", + "definition": "Quileute" + } + ] + }, + { + "code": "1086-8", + "display": "Chemehuevi", + "definition": "Chemehuevi" + }, + { + "code": "1088-4", + "display": "Cherokee", + "definition": "Cherokee", + "concept": [ + { + "code": "1089-2", + "display": "Cherokee Alabama", + "definition": "Cherokee Alabama" + }, + { + "code": "1090-0", + "display": "Cherokees of Northeast Alabama", + "definition": "Cherokees of Northeast Alabama" + }, + { + "code": "1091-8", + "display": "Cherokees of Southeast Alabama", + "definition": "Cherokees of Southeast Alabama" + }, + { + "code": "1092-6", + "display": "Eastern Cherokee", + "definition": "Eastern Cherokee" + }, + { + "code": "1093-4", + "display": "Echota Cherokee", + "definition": "Echota Cherokee" + }, + { + "code": "1094-2", + "display": "Etowah Cherokee", + "definition": "Etowah Cherokee" + }, + { + "code": "1095-9", + "display": "Northern Cherokee", + "definition": "Northern Cherokee" + }, + { + "code": "1096-7", + "display": "Tuscola", + "definition": "Tuscola" + }, + { + "code": "1097-5", + "display": "United Keetowah Band of Cherokee", + "definition": "United Keetowah Band of Cherokee" + }, + { + "code": "1098-3", + "display": "Western Cherokee", + "definition": "Western Cherokee" + } + ] + }, + { + "code": "1100-7", + "display": "Cherokee Shawnee", + "definition": "Cherokee Shawnee" + }, + { + "code": "1102-3", + "display": "Cheyenne", + "definition": "Cheyenne", + "concept": [ + { + "code": "1103-1", + "display": "Northern Cheyenne", + "definition": "Northern Cheyenne" + }, + { + "code": "1104-9", + "display": "Southern Cheyenne", + "definition": "Southern Cheyenne" + } + ] + }, + { + "code": "1106-4", + "display": "Cheyenne-Arapaho", + "definition": "Cheyenne-Arapaho" + }, + { + "code": "1108-0", + "display": "Chickahominy", + "definition": "Chickahominy", + "concept": [ + { + "code": "1109-8", + "display": "Eastern Chickahominy", + "definition": "Eastern Chickahominy" + }, + { + "code": "1110-6", + "display": "Western Chickahominy", + "definition": "Western Chickahominy" + } + ] + }, + { + "code": "1112-2", + "display": "Chickasaw", + "definition": "Chickasaw" + }, + { + "code": "1114-8", + "display": "Chinook", + "definition": "Chinook", + "concept": [ + { + "code": "1115-5", + "display": "Clatsop", + "definition": "Clatsop" + }, + { + "code": "1116-3", + "display": "Columbia River Chinook", + "definition": "Columbia River Chinook" + }, + { + "code": "1117-1", + "display": "Kathlamet", + "definition": "Kathlamet" + }, + { + "code": "1118-9", + "display": "Upper Chinook", + "definition": "Upper Chinook" + }, + { + "code": "1119-7", + "display": "Wakiakum Chinook", + "definition": "Wakiakum Chinook" + }, + { + "code": "1120-5", + "display": "Willapa Chinook", + "definition": "Willapa Chinook" + }, + { + "code": "1121-3", + "display": "Wishram", + "definition": "Wishram" + } + ] + }, + { + "code": "1123-9", + "display": "Chippewa", + "definition": "Chippewa", + "concept": [ + { + "code": "1124-7", + "display": "Bad River", + "definition": "Bad River" + }, + { + "code": "1125-4", + "display": "Bay Mills Chippewa", + "definition": "Bay Mills Chippewa" + }, + { + "code": "1126-2", + "display": "Bois Forte", + "definition": "Bois Forte" + }, + { + "code": "1127-0", + "display": "Burt Lake Chippewa", + "definition": "Burt Lake Chippewa" + }, + { + "code": "1128-8", + "display": "Fond du Lac", + "definition": "Fond du Lac" + }, + { + "code": "1129-6", + "display": "Grand Portage", + "definition": "Grand Portage" + }, + { + "code": "1130-4", + "display": "Grand Traverse Band of Ottawa-Chippewa", + "definition": "Grand Traverse Band of Ottawa-Chippewa" + }, + { + "code": "1131-2", + "display": "Keweenaw", + "definition": "Keweenaw" + }, + { + "code": "1132-0", + "display": "Lac Courte Oreilles", + "definition": "Lac Courte Oreilles" + }, + { + "code": "1133-8", + "display": "Lac du Flambeau", + "definition": "Lac du Flambeau" + }, + { + "code": "1134-6", + "display": "Lac Vieux Desert Chippewa", + "definition": "Lac Vieux Desert Chippewa" + }, + { + "code": "1135-3", + "display": "Lake Superior", + "definition": "Lake Superior" + }, + { + "code": "1136-1", + "display": "Leech Lake", + "definition": "Leech Lake" + }, + { + "code": "1137-9", + "display": "Little Shell Chippewa", + "definition": "Little Shell Chippewa" + }, + { + "code": "1138-7", + "display": "Mille Lacs", + "definition": "Mille Lacs" + }, + { + "code": "1139-5", + "display": "Minnesota Chippewa", + "definition": "Minnesota Chippewa" + }, + { + "code": "1140-3", + "display": "Ontonagon", + "definition": "Ontonagon" + }, + { + "code": "1141-1", + "display": "Red Cliff Chippewa", + "definition": "Red Cliff Chippewa" + }, + { + "code": "1142-9", + "display": "Red Lake Chippewa", + "definition": "Red Lake Chippewa" + }, + { + "code": "1143-7", + "display": "Saginaw Chippewa", + "definition": "Saginaw Chippewa" + }, + { + "code": "1144-5", + "display": "St. Croix Chippewa", + "definition": "St. Croix Chippewa" + }, + { + "code": "1145-2", + "display": "Sault Ste. Marie Chippewa", + "definition": "Sault Ste. Marie Chippewa" + }, + { + "code": "1146-0", + "display": "Sokoagon Chippewa", + "definition": "Sokoagon Chippewa" + }, + { + "code": "1147-8", + "display": "Turtle Mountain", + "definition": "Turtle Mountain" + }, + { + "code": "1148-6", + "display": "White Earth", + "definition": "White Earth" + } + ] + }, + { + "code": "1150-2", + "display": "Chippewa Cree", + "definition": "Chippewa Cree", + "concept": [ + { + "code": "1151-0", + "display": "Rocky Boy's Chippewa Cree", + "definition": "Rocky Boy's Chippewa Cree" + } + ] + }, + { + "code": "1153-6", + "display": "Chitimacha", + "definition": "Chitimacha" + }, + { + "code": "1155-1", + "display": "Choctaw", + "definition": "Choctaw", + "concept": [ + { + "code": "1156-9", + "display": "Clifton Choctaw", + "definition": "Clifton Choctaw" + }, + { + "code": "1157-7", + "display": "Jena Choctaw", + "definition": "Jena Choctaw" + }, + { + "code": "1158-5", + "display": "Mississippi Choctaw", + "definition": "Mississippi Choctaw" + }, + { + "code": "1159-3", + "display": "Mowa Band of Choctaw", + "definition": "Mowa Band of Choctaw" + }, + { + "code": "1160-1", + "display": "Oklahoma Choctaw", + "definition": "Oklahoma Choctaw" + } + ] + }, + { + "code": "1162-7", + "display": "Chumash", + "definition": "Chumash", + "concept": [ + { + "code": "1163-5", + "display": "Santa Ynez", + "definition": "Santa Ynez" + } + ] + }, + { + "code": "1165-0", + "display": "Clear Lake", + "definition": "Clear Lake" + }, + { + "code": "1167-6", + "display": "Coeur D'Alene", + "definition": "Coeur D'Alene" + }, + { + "code": "1169-2", + "display": "Coharie", + "definition": "Coharie" + }, + { + "code": "1171-8", + "display": "Colorado River", + "definition": "Colorado River" + }, + { + "code": "1173-4", + "display": "Colville", + "definition": "Colville" + }, + { + "code": "1175-9", + "display": "Comanche", + "definition": "Comanche", + "concept": [ + { + "code": "1176-7", + "display": "Oklahoma Comanche", + "definition": "Oklahoma Comanche" + } + ] + }, + { + "code": "1178-3", + "display": "Coos, Lower Umpqua, Siuslaw", + "definition": "Coos, Lower Umpqua, Siuslaw" + }, + { + "code": "1180-9", + "display": "Coos", + "definition": "Coos" + }, + { + "code": "1182-5", + "display": "Coquilles", + "definition": "Coquilles" + }, + { + "code": "1184-1", + "display": "Costanoan", + "definition": "Costanoan" + }, + { + "code": "1186-6", + "display": "Coushatta", + "definition": "Coushatta", + "concept": [ + { + "code": "1187-4", + "display": "Alabama Coushatta", + "definition": "Alabama Coushatta" + } + ] + }, + { + "code": "1189-0", + "display": "Cowlitz", + "definition": "Cowlitz" + }, + { + "code": "1191-6", + "display": "Cree", + "definition": "Cree" + }, + { + "code": "1193-2", + "display": "Creek", + "definition": "Creek", + "concept": [ + { + "code": "1194-0", + "display": "Alabama Creek", + "definition": "Alabama Creek" + }, + { + "code": "1195-7", + "display": "Alabama Quassarte", + "definition": "Alabama Quassarte" + }, + { + "code": "1196-5", + "display": "Eastern Creek", + "definition": "Eastern Creek" + }, + { + "code": "1197-3", + "display": "Eastern Muscogee", + "definition": "Eastern Muscogee" + }, + { + "code": "1198-1", + "display": "Kialegee", + "definition": "Kialegee" + }, + { + "code": "1199-9", + "display": "Lower Muscogee", + "definition": "Lower Muscogee" + }, + { + "code": "1200-5", + "display": "Machis Lower Creek Indian", + "definition": "Machis Lower Creek Indian" + }, + { + "code": "1201-3", + "display": "Poarch Band", + "definition": "Poarch Band" + }, + { + "code": "1202-1", + "display": "Principal Creek Indian Nation", + "definition": "Principal Creek Indian Nation" + }, + { + "code": "1203-9", + "display": "Star Clan of Muscogee Creeks", + "definition": "Star Clan of Muscogee Creeks" + }, + { + "code": "1204-7", + "display": "Thlopthlocco", + "definition": "Thlopthlocco" + }, + { + "code": "1205-4", + "display": "Tuckabachee", + "definition": "Tuckabachee" + } + ] + }, + { + "code": "1207-0", + "display": "Croatan", + "definition": "Croatan" + }, + { + "code": "1209-6", + "display": "Crow", + "definition": "Crow" + }, + { + "code": "1211-2", + "display": "Cupeno", + "definition": "Cupeno", + "concept": [ + { + "code": "1212-0", + "display": "Agua Caliente", + "definition": "Agua Caliente" + } + ] + }, + { + "code": "1214-6", + "display": "Delaware", + "definition": "Delaware", + "concept": [ + { + "code": "1215-3", + "display": "Eastern Delaware", + "definition": "Eastern Delaware" + }, + { + "code": "1216-1", + "display": "Lenni-Lenape", + "definition": "Lenni-Lenape" + }, + { + "code": "1217-9", + "display": "Munsee", + "definition": "Munsee" + }, + { + "code": "1218-7", + "display": "Oklahoma Delaware", + "definition": "Oklahoma Delaware" + }, + { + "code": "1219-5", + "display": "Rampough Mountain", + "definition": "Rampough Mountain" + }, + { + "code": "1220-3", + "display": "Sand Hill", + "definition": "Sand Hill" + } + ] + }, + { + "code": "1222-9", + "display": "Diegueno", + "definition": "Diegueno", + "concept": [ + { + "code": "1223-7", + "display": "Campo", + "definition": "Campo" + }, + { + "code": "1224-5", + "display": "Capitan Grande", + "definition": "Capitan Grande" + }, + { + "code": "1225-2", + "display": "Cuyapaipe", + "definition": "Cuyapaipe" + }, + { + "code": "1226-0", + "display": "La Posta", + "definition": "La Posta" + }, + { + "code": "1227-8", + "display": "Manzanita", + "definition": "Manzanita" + }, + { + "code": "1228-6", + "display": "Mesa Grande", + "definition": "Mesa Grande" + }, + { + "code": "1229-4", + "display": "San Pasqual", + "definition": "San Pasqual" + }, + { + "code": "1230-2", + "display": "Santa Ysabel", + "definition": "Santa Ysabel" + }, + { + "code": "1231-0", + "display": "Sycuan", + "definition": "Sycuan" + } + ] + }, + { + "code": "1233-6", + "display": "Eastern Tribes", + "definition": "Eastern Tribes", + "concept": [ + { + "code": "1234-4", + "display": "Attacapa", + "definition": "Attacapa" + }, + { + "code": "1235-1", + "display": "Biloxi", + "definition": "Biloxi" + }, + { + "code": "1236-9", + "display": "Georgetown", + "definition": "Georgetown" + }, + { + "code": "1237-7", + "display": "Moor", + "definition": "Moor" + }, + { + "code": "1238-5", + "display": "Nansemond", + "definition": "Nansemond" + }, + { + "code": "1239-3", + "display": "Natchez", + "definition": "Natchez" + }, + { + "code": "1240-1", + "display": "Nausu Waiwash", + "definition": "Nausu Waiwash" + }, + { + "code": "1241-9", + "display": "Nipmuc", + "definition": "Nipmuc" + }, + { + "code": "1242-7", + "display": "Paugussett", + "definition": "Paugussett" + }, + { + "code": "1243-5", + "display": "Pocomoke Acohonock", + "definition": "Pocomoke Acohonock" + }, + { + "code": "1244-3", + "display": "Southeastern Indians", + "definition": "Southeastern Indians" + }, + { + "code": "1245-0", + "display": "Susquehanock", + "definition": "Susquehanock" + }, + { + "code": "1246-8", + "display": "Tunica Biloxi", + "definition": "Tunica Biloxi" + }, + { + "code": "1247-6", + "display": "Waccamaw-Siousan", + "definition": "Waccamaw-Siousan" + }, + { + "code": "1248-4", + "display": "Wicomico", + "definition": "Wicomico" + } + ] + }, + { + "code": "1250-0", + "display": "Esselen", + "definition": "Esselen" + }, + { + "code": "1252-6", + "display": "Fort Belknap", + "definition": "Fort Belknap" + }, + { + "code": "1254-2", + "display": "Fort Berthold", + "definition": "Fort Berthold" + }, + { + "code": "1256-7", + "display": "Fort Mcdowell", + "definition": "Fort Mcdowell" + }, + { + "code": "1258-3", + "display": "Fort Hall", + "definition": "Fort Hall" + }, + { + "code": "1260-9", + "display": "Gabrieleno", + "definition": "Gabrieleno" + }, + { + "code": "1262-5", + "display": "Grand Ronde", + "definition": "Grand Ronde" + }, + { + "code": "1264-1", + "display": "Gros Ventres", + "definition": "Gros Ventres", + "concept": [ + { + "code": "1265-8", + "display": "Atsina", + "definition": "Atsina" + } + ] + }, + { + "code": "1267-4", + "display": "Haliwa", + "definition": "Haliwa" + }, + { + "code": "1269-0", + "display": "Hidatsa", + "definition": "Hidatsa" + }, + { + "code": "1271-6", + "display": "Hoopa", + "definition": "Hoopa", + "concept": [ + { + "code": "1272-4", + "display": "Trinity", + "definition": "Trinity" + }, + { + "code": "1273-2", + "display": "Whilkut", + "definition": "Whilkut" + } + ] + }, + { + "code": "1275-7", + "display": "Hoopa Extension", + "definition": "Hoopa Extension" + }, + { + "code": "1277-3", + "display": "Houma", + "definition": "Houma" + }, + { + "code": "1279-9", + "display": "Inaja-Cosmit", + "definition": "Inaja-Cosmit" + }, + { + "code": "1281-5", + "display": "Iowa", + "definition": "Iowa", + "concept": [ + { + "code": "1282-3", + "display": "Iowa of Kansas-Nebraska", + "definition": "Iowa of Kansas-Nebraska" + }, + { + "code": "1283-1", + "display": "Iowa of Oklahoma", + "definition": "Iowa of Oklahoma" + } + ] + }, + { + "code": "1285-6", + "display": "Iroquois", + "definition": "Iroquois", + "concept": [ + { + "code": "1286-4", + "display": "Cayuga", + "definition": "Cayuga" + }, + { + "code": "1287-2", + "display": "Mohawk", + "definition": "Mohawk" + }, + { + "code": "1288-0", + "display": "Oneida", + "definition": "Oneida" + }, + { + "code": "1289-8", + "display": "Onondaga", + "definition": "Onondaga" + }, + { + "code": "1290-6", + "display": "Seneca", + "definition": "Seneca" + }, + { + "code": "1291-4", + "display": "Seneca Nation", + "definition": "Seneca Nation" + }, + { + "code": "1292-2", + "display": "Seneca-Cayuga", + "definition": "Seneca-Cayuga" + }, + { + "code": "1293-0", + "display": "Tonawanda Seneca", + "definition": "Tonawanda Seneca" + }, + { + "code": "1294-8", + "display": "Tuscarora", + "definition": "Tuscarora" + }, + { + "code": "1295-5", + "display": "Wyandotte", + "definition": "Wyandotte" + } + ] + }, + { + "code": "1297-1", + "display": "Juaneno", + "definition": "Juaneno" + }, + { + "code": "1299-7", + "display": "Kalispel", + "definition": "Kalispel" + }, + { + "code": "1301-1", + "display": "Karuk", + "definition": "Karuk" + }, + { + "code": "1303-7", + "display": "Kaw", + "definition": "Kaw" + }, + { + "code": "1305-2", + "display": "Kickapoo", + "definition": "Kickapoo", + "concept": [ + { + "code": "1306-0", + "display": "Oklahoma Kickapoo", + "definition": "Oklahoma Kickapoo" + }, + { + "code": "1307-8", + "display": "Texas Kickapoo", + "definition": "Texas Kickapoo" + } + ] + }, + { + "code": "1309-4", + "display": "Kiowa", + "definition": "Kiowa", + "concept": [ + { + "code": "1310-2", + "display": "Oklahoma Kiowa", + "definition": "Oklahoma Kiowa" + } + ] + }, + { + "code": "1312-8", + "display": "Klallam", + "definition": "Klallam", + "concept": [ + { + "code": "1313-6", + "display": "Jamestown", + "definition": "Jamestown" + }, + { + "code": "1314-4", + "display": "Lower Elwha", + "definition": "Lower Elwha" + }, + { + "code": "1315-1", + "display": "Port Gamble Klallam", + "definition": "Port Gamble Klallam" + } + ] + }, + { + "code": "1317-7", + "display": "Klamath", + "definition": "Klamath" + }, + { + "code": "1319-3", + "display": "Konkow", + "definition": "Konkow" + }, + { + "code": "1321-9", + "display": "Kootenai", + "definition": "Kootenai" + }, + { + "code": "1323-5", + "display": "Lassik", + "definition": "Lassik" + }, + { + "code": "1325-0", + "display": "Long Island", + "definition": "Long Island", + "concept": [ + { + "code": "1326-8", + "display": "Matinecock", + "definition": "Matinecock" + }, + { + "code": "1327-6", + "display": "Montauk", + "definition": "Montauk" + }, + { + "code": "1328-4", + "display": "Poospatuck", + "definition": "Poospatuck" + }, + { + "code": "1329-2", + "display": "Setauket", + "definition": "Setauket" + } + ] + }, + { + "code": "1331-8", + "display": "Luiseno", + "definition": "Luiseno", + "concept": [ + { + "code": "1332-6", + "display": "La Jolla", + "definition": "La Jolla" + }, + { + "code": "1333-4", + "display": "Pala", + "definition": "Pala" + }, + { + "code": "1334-2", + "display": "Pauma", + "definition": "Pauma" + }, + { + "code": "1335-9", + "display": "Pechanga", + "definition": "Pechanga" + }, + { + "code": "1336-7", + "display": "Soboba", + "definition": "Soboba" + }, + { + "code": "1337-5", + "display": "Twenty-Nine Palms", + "definition": "Twenty-Nine Palms" + }, + { + "code": "1338-3", + "display": "Temecula", + "definition": "Temecula" + } + ] + }, + { + "code": "1340-9", + "display": "Lumbee", + "definition": "Lumbee" + }, + { + "code": "1342-5", + "display": "Lummi", + "definition": "Lummi" + }, + { + "code": "1344-1", + "display": "Maidu", + "definition": "Maidu", + "concept": [ + { + "code": "1345-8", + "display": "Mountain Maidu", + "definition": "Mountain Maidu" + }, + { + "code": "1346-6", + "display": "Nishinam", + "definition": "Nishinam" + } + ] + }, + { + "code": "1348-2", + "display": "Makah", + "definition": "Makah" + }, + { + "code": "1350-8", + "display": "Maliseet", + "definition": "Maliseet" + }, + { + "code": "1352-4", + "display": "Mandan", + "definition": "Mandan" + }, + { + "code": "1354-0", + "display": "Mattaponi", + "definition": "Mattaponi" + }, + { + "code": "1356-5", + "display": "Menominee", + "definition": "Menominee" + }, + { + "code": "1358-1", + "display": "Miami", + "definition": "Miami", + "concept": [ + { + "code": "1359-9", + "display": "Illinois Miami", + "definition": "Illinois Miami" + }, + { + "code": "1360-7", + "display": "Indiana Miami", + "definition": "Indiana Miami" + }, + { + "code": "1361-5", + "display": "Oklahoma Miami", + "definition": "Oklahoma Miami" + } + ] + }, + { + "code": "1363-1", + "display": "Miccosukee", + "definition": "Miccosukee" + }, + { + "code": "1365-6", + "display": "Micmac", + "definition": "Micmac", + "concept": [ + { + "code": "1366-4", + "display": "Aroostook", + "definition": "Aroostook" + } + ] + }, + { + "code": "1368-0", + "display": "Mission Indians", + "definition": "Mission Indians" + }, + { + "code": "1370-6", + "display": "Miwok", + "definition": "Miwok" + }, + { + "code": "1372-2", + "display": "Modoc", + "definition": "Modoc" + }, + { + "code": "1374-8", + "display": "Mohegan", + "definition": "Mohegan" + }, + { + "code": "1376-3", + "display": "Mono", + "definition": "Mono" + }, + { + "code": "1378-9", + "display": "Nanticoke", + "definition": "Nanticoke" + }, + { + "code": "1380-5", + "display": "Narragansett", + "definition": "Narragansett" + }, + { + "code": "1382-1", + "display": "Navajo", + "definition": "Navajo", + "concept": [ + { + "code": "1383-9", + "display": "Alamo Navajo", + "definition": "Alamo Navajo" + }, + { + "code": "1384-7", + "display": "Canoncito Navajo", + "definition": "Canoncito Navajo" + }, + { + "code": "1385-4", + "display": "Ramah Navajo", + "definition": "Ramah Navajo" + } + ] + }, + { + "code": "1387-0", + "display": "Nez Perce", + "definition": "Nez Perce" + }, + { + "code": "1389-6", + "display": "Nomalaki", + "definition": "Nomalaki" + }, + { + "code": "1391-2", + "display": "Northwest Tribes", + "definition": "Northwest Tribes", + "concept": [ + { + "code": "1392-0", + "display": "Alsea", + "definition": "Alsea" + }, + { + "code": "1393-8", + "display": "Celilo", + "definition": "Celilo" + }, + { + "code": "1394-6", + "display": "Columbia", + "definition": "Columbia" + }, + { + "code": "1395-3", + "display": "Kalapuya", + "definition": "Kalapuya" + }, + { + "code": "1396-1", + "display": "Molala", + "definition": "Molala" + }, + { + "code": "1397-9", + "display": "Talakamish", + "definition": "Talakamish" + }, + { + "code": "1398-7", + "display": "Tenino", + "definition": "Tenino" + }, + { + "code": "1399-5", + "display": "Tillamook", + "definition": "Tillamook" + }, + { + "code": "1400-1", + "display": "Wenatchee", + "definition": "Wenatchee" + }, + { + "code": "1401-9", + "display": "Yahooskin", + "definition": "Yahooskin" + } + ] + }, + { + "code": "1403-5", + "display": "Omaha", + "definition": "Omaha" + }, + { + "code": "1405-0", + "display": "Oregon Athabaskan", + "definition": "Oregon Athabaskan" + }, + { + "code": "1407-6", + "display": "Osage", + "definition": "Osage" + }, + { + "code": "1409-2", + "display": "Otoe-Missouria", + "definition": "Otoe-Missouria" + }, + { + "code": "1411-8", + "display": "Ottawa", + "definition": "Ottawa", + "concept": [ + { + "code": "1412-6", + "display": "Burt Lake Ottawa", + "definition": "Burt Lake Ottawa" + }, + { + "code": "1413-4", + "display": "Michigan Ottawa", + "definition": "Michigan Ottawa" + }, + { + "code": "1414-2", + "display": "Oklahoma Ottawa", + "definition": "Oklahoma Ottawa" + } + ] + }, + { + "code": "1416-7", + "display": "Paiute", + "definition": "Paiute", + "concept": [ + { + "code": "1417-5", + "display": "Bishop", + "definition": "Bishop" + }, + { + "code": "1418-3", + "display": "Bridgeport", + "definition": "Bridgeport" + }, + { + "code": "1419-1", + "display": "Burns Paiute", + "definition": "Burns Paiute" + }, + { + "code": "1420-9", + "display": "Cedarville", + "definition": "Cedarville" + }, + { + "code": "1421-7", + "display": "Fort Bidwell", + "definition": "Fort Bidwell" + }, + { + "code": "1422-5", + "display": "Fort Independence", + "definition": "Fort Independence" + }, + { + "code": "1423-3", + "display": "Kaibab", + "definition": "Kaibab" + }, + { + "code": "1424-1", + "display": "Las Vegas", + "definition": "Las Vegas" + }, + { + "code": "1425-8", + "display": "Lone Pine", + "definition": "Lone Pine" + }, + { + "code": "1426-6", + "display": "Lovelock", + "definition": "Lovelock" + }, + { + "code": "1427-4", + "display": "Malheur Paiute", + "definition": "Malheur Paiute" + }, + { + "code": "1428-2", + "display": "Moapa", + "definition": "Moapa" + }, + { + "code": "1429-0", + "display": "Northern Paiute", + "definition": "Northern Paiute" + }, + { + "code": "1430-8", + "display": "Owens Valley", + "definition": "Owens Valley" + }, + { + "code": "1431-6", + "display": "Pyramid Lake", + "definition": "Pyramid Lake" + }, + { + "code": "1432-4", + "display": "San Juan Southern Paiute", + "definition": "San Juan Southern Paiute" + }, + { + "code": "1433-2", + "display": "Southern Paiute", + "definition": "Southern Paiute" + }, + { + "code": "1434-0", + "display": "Summit Lake", + "definition": "Summit Lake" + }, + { + "code": "1435-7", + "display": "Utu Utu Gwaitu Paiute", + "definition": "Utu Utu Gwaitu Paiute" + }, + { + "code": "1436-5", + "display": "Walker River", + "definition": "Walker River" + }, + { + "code": "1437-3", + "display": "Yerington Paiute", + "definition": "Yerington Paiute" + } + ] + }, + { + "code": "1439-9", + "display": "Pamunkey", + "definition": "Pamunkey" + }, + { + "code": "1441-5", + "display": "Passamaquoddy", + "definition": "Passamaquoddy", + "concept": [ + { + "code": "1442-3", + "display": "Indian Township", + "definition": "Indian Township" + }, + { + "code": "1443-1", + "display": "Pleasant Point Passamaquoddy", + "definition": "Pleasant Point Passamaquoddy" + } + ] + }, + { + "code": "1445-6", + "display": "Pawnee", + "definition": "Pawnee", + "concept": [ + { + "code": "1446-4", + "display": "Oklahoma Pawnee", + "definition": "Oklahoma Pawnee" + } + ] + }, + { + "code": "1448-0", + "display": "Penobscot", + "definition": "Penobscot" + }, + { + "code": "1450-6", + "display": "Peoria", + "definition": "Peoria", + "concept": [ + { + "code": "1451-4", + "display": "Oklahoma Peoria", + "definition": "Oklahoma Peoria" + } + ] + }, + { + "code": "1453-0", + "display": "Pequot", + "definition": "Pequot", + "concept": [ + { + "code": "1454-8", + "display": "Marshantucket Pequot", + "definition": "Marshantucket Pequot" + } + ] + }, + { + "code": "1456-3", + "display": "Pima", + "definition": "Pima", + "concept": [ + { + "code": "1457-1", + "display": "Gila River Pima-Maricopa", + "definition": "Gila River Pima-Maricopa" + }, + { + "code": "1458-9", + "display": "Salt River Pima-Maricopa", + "definition": "Salt River Pima-Maricopa" + } + ] + }, + { + "code": "1460-5", + "display": "Piscataway", + "definition": "Piscataway" + }, + { + "code": "1462-1", + "display": "Pit River", + "definition": "Pit River" + }, + { + "code": "1464-7", + "display": "Pomo", + "definition": "Pomo", + "concept": [ + { + "code": "1465-4", + "display": "Central Pomo", + "definition": "Central Pomo" + }, + { + "code": "1466-2", + "display": "Dry Creek", + "definition": "Dry Creek" + }, + { + "code": "1467-0", + "display": "Eastern Pomo", + "definition": "Eastern Pomo" + }, + { + "code": "1468-8", + "display": "Kashia", + "definition": "Kashia" + }, + { + "code": "1469-6", + "display": "Northern Pomo", + "definition": "Northern Pomo" + }, + { + "code": "1470-4", + "display": "Scotts Valley", + "definition": "Scotts Valley" + }, + { + "code": "1471-2", + "display": "Stonyford", + "definition": "Stonyford" + }, + { + "code": "1472-0", + "display": "Sulphur Bank", + "definition": "Sulphur Bank" + } + ] + }, + { + "code": "1474-6", + "display": "Ponca", + "definition": "Ponca", + "concept": [ + { + "code": "1475-3", + "display": "Nebraska Ponca", + "definition": "Nebraska Ponca" + }, + { + "code": "1476-1", + "display": "Oklahoma Ponca", + "definition": "Oklahoma Ponca" + } + ] + }, + { + "code": "1478-7", + "display": "Potawatomi", + "definition": "Potawatomi", + "concept": [ + { + "code": "1479-5", + "display": "Citizen Band Potawatomi", + "definition": "Citizen Band Potawatomi" + }, + { + "code": "1480-3", + "display": "Forest County", + "definition": "Forest County" + }, + { + "code": "1481-1", + "display": "Hannahville", + "definition": "Hannahville" + }, + { + "code": "1482-9", + "display": "Huron Potawatomi", + "definition": "Huron Potawatomi" + }, + { + "code": "1483-7", + "display": "Pokagon Potawatomi", + "definition": "Pokagon Potawatomi" + }, + { + "code": "1484-5", + "display": "Prairie Band", + "definition": "Prairie Band" + }, + { + "code": "1485-2", + "display": "Wisconsin Potawatomi", + "definition": "Wisconsin Potawatomi" + } + ] + }, + { + "code": "1487-8", + "display": "Powhatan", + "definition": "Powhatan" + }, + { + "code": "1489-4", + "display": "Pueblo", + "definition": "Pueblo", + "concept": [ + { + "code": "1490-2", + "display": "Acoma", + "definition": "Acoma" + }, + { + "code": "1491-0", + "display": "Arizona Tewa", + "definition": "Arizona Tewa" + }, + { + "code": "1492-8", + "display": "Cochiti", + "definition": "Cochiti" + }, + { + "code": "1493-6", + "display": "Hopi", + "definition": "Hopi" + }, + { + "code": "1494-4", + "display": "Isleta", + "definition": "Isleta" + }, + { + "code": "1495-1", + "display": "Jemez", + "definition": "Jemez" + }, + { + "code": "1496-9", + "display": "Keres", + "definition": "Keres" + }, + { + "code": "1497-7", + "display": "Laguna", + "definition": "Laguna" + }, + { + "code": "1498-5", + "display": "Nambe", + "definition": "Nambe" + }, + { + "code": "1499-3", + "display": "Picuris", + "definition": "Picuris" + }, + { + "code": "1500-8", + "display": "Piro", + "definition": "Piro" + }, + { + "code": "1501-6", + "display": "Pojoaque", + "definition": "Pojoaque" + }, + { + "code": "1502-4", + "display": "San Felipe", + "definition": "San Felipe" + }, + { + "code": "1503-2", + "display": "San Ildefonso", + "definition": "San Ildefonso" + }, + { + "code": "1504-0", + "display": "San Juan Pueblo", + "definition": "San Juan Pueblo" + }, + { + "code": "1505-7", + "display": "San Juan De", + "definition": "San Juan De" + }, + { + "code": "1506-5", + "display": "San Juan", + "definition": "San Juan" + }, + { + "code": "1507-3", + "display": "Sandia", + "definition": "Sandia" + }, + { + "code": "1508-1", + "display": "Santa Ana", + "definition": "Santa Ana" + }, + { + "code": "1509-9", + "display": "Santa Clara", + "definition": "Santa Clara" + }, + { + "code": "1510-7", + "display": "Santo Domingo", + "definition": "Santo Domingo" + }, + { + "code": "1511-5", + "display": "Taos", + "definition": "Taos" + }, + { + "code": "1512-3", + "display": "Tesuque", + "definition": "Tesuque" + }, + { + "code": "1513-1", + "display": "Tewa", + "definition": "Tewa" + }, + { + "code": "1514-9", + "display": "Tigua", + "definition": "Tigua" + }, + { + "code": "1515-6", + "display": "Zia", + "definition": "Zia" + }, + { + "code": "1516-4", + "display": "Zuni", + "definition": "Zuni" + } + ] + }, + { + "code": "1518-0", + "display": "Puget Sound Salish", + "definition": "Puget Sound Salish", + "concept": [ + { + "code": "1519-8", + "display": "Duwamish", + "definition": "Duwamish" + }, + { + "code": "1520-6", + "display": "Kikiallus", + "definition": "Kikiallus" + }, + { + "code": "1521-4", + "display": "Lower Skagit", + "definition": "Lower Skagit" + }, + { + "code": "1522-2", + "display": "Muckleshoot", + "definition": "Muckleshoot" + }, + { + "code": "1523-0", + "display": "Nisqually", + "definition": "Nisqually" + }, + { + "code": "1524-8", + "display": "Nooksack", + "definition": "Nooksack" + }, + { + "code": "1525-5", + "display": "Port Madison", + "definition": "Port Madison" + }, + { + "code": "1526-3", + "display": "Puyallup", + "definition": "Puyallup" + }, + { + "code": "1527-1", + "display": "Samish", + "definition": "Samish" + }, + { + "code": "1528-9", + "display": "Sauk-Suiattle", + "definition": "Sauk-Suiattle" + }, + { + "code": "1529-7", + "display": "Skokomish", + "definition": "Skokomish" + }, + { + "code": "1530-5", + "display": "Skykomish", + "definition": "Skykomish" + }, + { + "code": "1531-3", + "display": "Snohomish", + "definition": "Snohomish" + }, + { + "code": "1532-1", + "display": "Snoqualmie", + "definition": "Snoqualmie" + }, + { + "code": "1533-9", + "display": "Squaxin Island", + "definition": "Squaxin Island" + }, + { + "code": "1534-7", + "display": "Steilacoom", + "definition": "Steilacoom" + }, + { + "code": "1535-4", + "display": "Stillaguamish", + "definition": "Stillaguamish" + }, + { + "code": "1536-2", + "display": "Suquamish", + "definition": "Suquamish" + }, + { + "code": "1537-0", + "display": "Swinomish", + "definition": "Swinomish" + }, + { + "code": "1538-8", + "display": "Tulalip", + "definition": "Tulalip" + }, + { + "code": "1539-6", + "display": "Upper Skagit", + "definition": "Upper Skagit" + } + ] + }, + { + "code": "1541-2", + "display": "Quapaw", + "definition": "Quapaw" + }, + { + "code": "1543-8", + "display": "Quinault", + "definition": "Quinault" + }, + { + "code": "1545-3", + "display": "Rappahannock", + "definition": "Rappahannock" + }, + { + "code": "1547-9", + "display": "Reno-Sparks", + "definition": "Reno-Sparks" + }, + { + "code": "1549-5", + "display": "Round Valley", + "definition": "Round Valley" + }, + { + "code": "1551-1", + "display": "Sac and Fox", + "definition": "Sac and Fox", + "concept": [ + { + "code": "1552-9", + "display": "Iowa Sac and Fox", + "definition": "Iowa Sac and Fox" + }, + { + "code": "1553-7", + "display": "Missouri Sac and Fox", + "definition": "Missouri Sac and Fox" + }, + { + "code": "1554-5", + "display": "Oklahoma Sac and Fox", + "definition": "Oklahoma Sac and Fox" + } + ] + }, + { + "code": "1556-0", + "display": "Salinan", + "definition": "Salinan" + }, + { + "code": "1558-6", + "display": "Salish", + "definition": "Salish" + }, + { + "code": "1560-2", + "display": "Salish and Kootenai", + "definition": "Salish and Kootenai" + }, + { + "code": "1562-8", + "display": "Schaghticoke", + "definition": "Schaghticoke" + }, + { + "code": "1564-4", + "display": "Scott Valley", + "definition": "Scott Valley" + }, + { + "code": "1566-9", + "display": "Seminole", + "definition": "Seminole", + "concept": [ + { + "code": "1567-7", + "display": "Big Cypress", + "definition": "Big Cypress" + }, + { + "code": "1568-5", + "display": "Brighton", + "definition": "Brighton" + }, + { + "code": "1569-3", + "display": "Florida Seminole", + "definition": "Florida Seminole" + }, + { + "code": "1570-1", + "display": "Hollywood Seminole", + "definition": "Hollywood Seminole" + }, + { + "code": "1571-9", + "display": "Oklahoma Seminole", + "definition": "Oklahoma Seminole" + } + ] + }, + { + "code": "1573-5", + "display": "Serrano", + "definition": "Serrano", + "concept": [ + { + "code": "1574-3", + "display": "San Manual", + "definition": "San Manual" + } + ] + }, + { + "code": "1576-8", + "display": "Shasta", + "definition": "Shasta" + }, + { + "code": "1578-4", + "display": "Shawnee", + "definition": "Shawnee", + "concept": [ + { + "code": "1579-2", + "display": "Absentee Shawnee", + "definition": "Absentee Shawnee" + }, + { + "code": "1580-0", + "display": "Eastern Shawnee", + "definition": "Eastern Shawnee" + } + ] + }, + { + "code": "1582-6", + "display": "Shinnecock", + "definition": "Shinnecock" + }, + { + "code": "1584-2", + "display": "Shoalwater Bay", + "definition": "Shoalwater Bay" + }, + { + "code": "1586-7", + "display": "Shoshone", + "definition": "Shoshone", + "concept": [ + { + "code": "1587-5", + "display": "Battle Mountain", + "definition": "Battle Mountain" + }, + { + "code": "1588-3", + "display": "Duckwater", + "definition": "Duckwater" + }, + { + "code": "1589-1", + "display": "Elko", + "definition": "Elko" + }, + { + "code": "1590-9", + "display": "Ely", + "definition": "Ely" + }, + { + "code": "1591-7", + "display": "Goshute", + "definition": "Goshute" + }, + { + "code": "1592-5", + "display": "Panamint", + "definition": "Panamint" + }, + { + "code": "1593-3", + "display": "Ruby Valley", + "definition": "Ruby Valley" + }, + { + "code": "1594-1", + "display": "Skull Valley", + "definition": "Skull Valley" + }, + { + "code": "1595-8", + "display": "South Fork Shoshone", + "definition": "South Fork Shoshone" + }, + { + "code": "1596-6", + "display": "Te-Moak Western Shoshone", + "definition": "Te-Moak Western Shoshone" + }, + { + "code": "1597-4", + "display": "Timbi-Sha Shoshone", + "definition": "Timbi-Sha Shoshone" + }, + { + "code": "1598-2", + "display": "Washakie", + "definition": "Washakie" + }, + { + "code": "1599-0", + "display": "Wind River Shoshone", + "definition": "Wind River Shoshone" + }, + { + "code": "1600-6", + "display": "Yomba", + "definition": "Yomba" + } + ] + }, + { + "code": "1602-2", + "display": "Shoshone Paiute", + "definition": "Shoshone Paiute", + "concept": [ + { + "code": "1603-0", + "display": "Duck Valley", + "definition": "Duck Valley" + }, + { + "code": "1604-8", + "display": "Fallon", + "definition": "Fallon" + }, + { + "code": "1605-5", + "display": "Fort McDermitt", + "definition": "Fort McDermitt" + } + ] + }, + { + "code": "1607-1", + "display": "Siletz", + "definition": "Siletz" + }, + { + "code": "1609-7", + "display": "Sioux", + "definition": "Sioux", + "concept": [ + { + "code": "1610-5", + "display": "Blackfoot Sioux", + "definition": "Blackfoot Sioux" + }, + { + "code": "1611-3", + "display": "Brule Sioux", + "definition": "Brule Sioux" + }, + { + "code": "1612-1", + "display": "Cheyenne River Sioux", + "definition": "Cheyenne River Sioux" + }, + { + "code": "1613-9", + "display": "Crow Creek Sioux", + "definition": "Crow Creek Sioux" + }, + { + "code": "1614-7", + "display": "Dakota Sioux", + "definition": "Dakota Sioux" + }, + { + "code": "1615-4", + "display": "Flandreau Santee", + "definition": "Flandreau Santee" + }, + { + "code": "1616-2", + "display": "Fort Peck", + "definition": "Fort Peck" + }, + { + "code": "1617-0", + "display": "Lake Traverse Sioux", + "definition": "Lake Traverse Sioux" + }, + { + "code": "1618-8", + "display": "Lower Brule Sioux", + "definition": "Lower Brule Sioux" + }, + { + "code": "1619-6", + "display": "Lower Sioux", + "definition": "Lower Sioux" + }, + { + "code": "1620-4", + "display": "Mdewakanton Sioux", + "definition": "Mdewakanton Sioux" + }, + { + "code": "1621-2", + "display": "Miniconjou", + "definition": "Miniconjou" + }, + { + "code": "1622-0", + "display": "Oglala Sioux", + "definition": "Oglala Sioux" + }, + { + "code": "1623-8", + "display": "Pine Ridge Sioux", + "definition": "Pine Ridge Sioux" + }, + { + "code": "1624-6", + "display": "Pipestone Sioux", + "definition": "Pipestone Sioux" + }, + { + "code": "1625-3", + "display": "Prairie Island Sioux", + "definition": "Prairie Island Sioux" + }, + { + "code": "1626-1", + "display": "Prior Lake Sioux", + "definition": "Prior Lake Sioux" + }, + { + "code": "1627-9", + "display": "Rosebud Sioux", + "definition": "Rosebud Sioux" + }, + { + "code": "1628-7", + "display": "Sans Arc Sioux", + "definition": "Sans Arc Sioux" + }, + { + "code": "1629-5", + "display": "Santee Sioux", + "definition": "Santee Sioux" + }, + { + "code": "1630-3", + "display": "Sisseton-Wahpeton", + "definition": "Sisseton-Wahpeton" + }, + { + "code": "1631-1", + "display": "Sisseton Sioux", + "definition": "Sisseton Sioux" + }, + { + "code": "1632-9", + "display": "Spirit Lake Sioux", + "definition": "Spirit Lake Sioux" + }, + { + "code": "1633-7", + "display": "Standing Rock Sioux", + "definition": "Standing Rock Sioux" + }, + { + "code": "1634-5", + "display": "Teton Sioux", + "definition": "Teton Sioux" + }, + { + "code": "1635-2", + "display": "Two Kettle Sioux", + "definition": "Two Kettle Sioux" + }, + { + "code": "1636-0", + "display": "Upper Sioux", + "definition": "Upper Sioux" + }, + { + "code": "1637-8", + "display": "Wahpekute Sioux", + "definition": "Wahpekute Sioux" + }, + { + "code": "1638-6", + "display": "Wahpeton Sioux", + "definition": "Wahpeton Sioux" + }, + { + "code": "1639-4", + "display": "Wazhaza Sioux", + "definition": "Wazhaza Sioux" + }, + { + "code": "1640-2", + "display": "Yankton Sioux", + "definition": "Yankton Sioux" + }, + { + "code": "1641-0", + "display": "Yanktonai Sioux", + "definition": "Yanktonai Sioux" + } + ] + }, + { + "code": "1643-6", + "display": "Siuslaw", + "definition": "Siuslaw" + }, + { + "code": "1645-1", + "display": "Spokane", + "definition": "Spokane" + }, + { + "code": "1647-7", + "display": "Stewart", + "definition": "Stewart" + }, + { + "code": "1649-3", + "display": "Stockbridge", + "definition": "Stockbridge" + }, + { + "code": "1651-9", + "display": "Susanville", + "definition": "Susanville" + }, + { + "code": "1653-5", + "display": "Tohono O'Odham", + "definition": "Tohono O'Odham", + "concept": [ + { + "code": "1654-3", + "display": "Ak-Chin", + "definition": "Ak-Chin" + }, + { + "code": "1655-0", + "display": "Gila Bend", + "definition": "Gila Bend" + }, + { + "code": "1656-8", + "display": "San Xavier", + "definition": "San Xavier" + }, + { + "code": "1657-6", + "display": "Sells", + "definition": "Sells" + } + ] + }, + { + "code": "1659-2", + "display": "Tolowa", + "definition": "Tolowa" + }, + { + "code": "1661-8", + "display": "Tonkawa", + "definition": "Tonkawa" + }, + { + "code": "1663-4", + "display": "Tygh", + "definition": "Tygh" + }, + { + "code": "1665-9", + "display": "Umatilla", + "definition": "Umatilla" + }, + { + "code": "1667-5", + "display": "Umpqua", + "definition": "Umpqua", + "concept": [ + { + "code": "1668-3", + "display": "Cow Creek Umpqua", + "definition": "Cow Creek Umpqua" + } + ] + }, + { + "code": "1670-9", + "display": "Ute", + "definition": "Ute", + "concept": [ + { + "code": "1671-7", + "display": "Allen Canyon", + "definition": "Allen Canyon" + }, + { + "code": "1672-5", + "display": "Uintah Ute", + "definition": "Uintah Ute" + }, + { + "code": "1673-3", + "display": "Ute Mountain Ute", + "definition": "Ute Mountain Ute" + } + ] + }, + { + "code": "1675-8", + "display": "Wailaki", + "definition": "Wailaki" + }, + { + "code": "1677-4", + "display": "Walla-Walla", + "definition": "Walla-Walla" + }, + { + "code": "1679-0", + "display": "Wampanoag", + "definition": "Wampanoag", + "concept": [ + { + "code": "1680-8", + "display": "Gay Head Wampanoag", + "definition": "Gay Head Wampanoag" + }, + { + "code": "1681-6", + "display": "Mashpee Wampanoag", + "definition": "Mashpee Wampanoag" + } + ] + }, + { + "code": "1683-2", + "display": "Warm Springs", + "definition": "Warm Springs" + }, + { + "code": "1685-7", + "display": "Wascopum", + "definition": "Wascopum" + }, + { + "code": "1687-3", + "display": "Washoe", + "definition": "Washoe", + "concept": [ + { + "code": "1688-1", + "display": "Alpine", + "definition": "Alpine" + }, + { + "code": "1689-9", + "display": "Carson", + "definition": "Carson" + }, + { + "code": "1690-7", + "display": "Dresslerville", + "definition": "Dresslerville" + } + ] + }, + { + "code": "1692-3", + "display": "Wichita", + "definition": "Wichita" + }, + { + "code": "1694-9", + "display": "Wind River", + "definition": "Wind River" + }, + { + "code": "1696-4", + "display": "Winnebago", + "definition": "Winnebago", + "concept": [ + { + "code": "1697-2", + "display": "Ho-chunk", + "definition": "Ho-chunk" + }, + { + "code": "1698-0", + "display": "Nebraska Winnebago", + "definition": "Nebraska Winnebago" + } + ] + }, + { + "code": "1700-4", + "display": "Winnemucca", + "definition": "Winnemucca" + }, + { + "code": "1702-0", + "display": "Wintun", + "definition": "Wintun" + }, + { + "code": "1704-6", + "display": "Wiyot", + "definition": "Wiyot", + "concept": [ + { + "code": "1705-3", + "display": "Table Bluff", + "definition": "Table Bluff" + } + ] + }, + { + "code": "1707-9", + "display": "Yakama", + "definition": "Yakama" + }, + { + "code": "1709-5", + "display": "Yakama Cowlitz", + "definition": "Yakama Cowlitz" + }, + { + "code": "1711-1", + "display": "Yaqui", + "definition": "Yaqui", + "concept": [ + { + "code": "1712-9", + "display": "Barrio Libre", + "definition": "Barrio Libre" + }, + { + "code": "1713-7", + "display": "Pascua Yaqui", + "definition": "Pascua Yaqui" + } + ] + }, + { + "code": "1715-2", + "display": "Yavapai Apache", + "definition": "Yavapai Apache" + }, + { + "code": "1717-8", + "display": "Yokuts", + "definition": "Yokuts", + "concept": [ + { + "code": "1718-6", + "display": "Chukchansi", + "definition": "Chukchansi" + }, + { + "code": "1719-4", + "display": "Tachi", + "definition": "Tachi" + }, + { + "code": "1720-2", + "display": "Tule River", + "definition": "Tule River" + } + ] + }, + { + "code": "1722-8", + "display": "Yuchi", + "definition": "Yuchi" + }, + { + "code": "1724-4", + "display": "Yuman", + "definition": "Yuman", + "concept": [ + { + "code": "1725-1", + "display": "Cocopah", + "definition": "Cocopah" + }, + { + "code": "1726-9", + "display": "Havasupai", + "definition": "Havasupai" + }, + { + "code": "1727-7", + "display": "Hualapai", + "definition": "Hualapai" + }, + { + "code": "1728-5", + "display": "Maricopa", + "definition": "Maricopa" + }, + { + "code": "1729-3", + "display": "Mohave", + "definition": "Mohave" + }, + { + "code": "1730-1", + "display": "Quechan", + "definition": "Quechan" + }, + { + "code": "1731-9", + "display": "Yavapai", + "definition": "Yavapai" + } + ] + }, + { + "code": "1732-7", + "display": "Yurok", + "definition": "Yurok", + "concept": [ + { + "code": "1733-5", + "display": "Coast Yurok", + "definition": "Coast Yurok" + } + ] + } + ] + }, + { + "code": "1735-0", + "display": "Alaska Native", + "definition": "Alaska Native", + "concept": [ + { + "code": "1737-6", + "display": "Alaska Indian", + "definition": "Alaska Indian", + "concept": [ + { + "code": "1739-2", + "display": "Alaskan Athabascan", + "definition": "Alaskan Athabascan", + "concept": [ + { + "code": "1740-0", + "display": "Ahtna", + "definition": "Ahtna" + } + ] + }, + { + "code": "1811-9", + "display": "Southeast Alaska", + "definition": "Southeast Alaska", + "concept": [ + { + "code": "1813-5", + "display": "Tlingit-Haida", + "definition": "Tlingit-Haida", + "concept": [ + { + "code": "1814-3", + "display": "Angoon", + "definition": "Angoon" + }, + { + "code": "1815-0", + "display": "Central Council of Tlingit and Haida Tribes", + "definition": "Central Council of Tlingit and Haida Tribes" + }, + { + "code": "1816-8", + "display": "Chilkat", + "definition": "Chilkat" + }, + { + "code": "1817-6", + "display": "Chilkoot", + "definition": "Chilkoot" + }, + { + "code": "1818-4", + "display": "Craig", + "definition": "Craig" + }, + { + "code": "1819-2", + "display": "Douglas", + "definition": "Douglas" + }, + { + "code": "1820-0", + "display": "Haida", + "definition": "Haida" + }, + { + "code": "1821-8", + "display": "Hoonah", + "definition": "Hoonah" + }, + { + "code": "1822-6", + "display": "Hydaburg", + "definition": "Hydaburg" + }, + { + "code": "1823-4", + "display": "Kake", + "definition": "Kake" + }, + { + "code": "1824-2", + "display": "Kasaan", + "definition": "Kasaan" + }, + { + "code": "1825-9", + "display": "Kenaitze", + "definition": "Kenaitze" + }, + { + "code": "1826-7", + "display": "Ketchikan", + "definition": "Ketchikan" + }, + { + "code": "1827-5", + "display": "Klawock", + "definition": "Klawock" + }, + { + "code": "1828-3", + "display": "Pelican", + "definition": "Pelican" + }, + { + "code": "1829-1", + "display": "Petersburg", + "definition": "Petersburg" + }, + { + "code": "1830-9", + "display": "Saxman", + "definition": "Saxman" + }, + { + "code": "1831-7", + "display": "Sitka", + "definition": "Sitka" + }, + { + "code": "1832-5", + "display": "Tenakee Springs", + "definition": "Tenakee Springs" + }, + { + "code": "1833-3", + "display": "Tlingit", + "definition": "Tlingit" + }, + { + "code": "1834-1", + "display": "Wrangell", + "definition": "Wrangell" + }, + { + "code": "1835-8", + "display": "Yakutat", + "definition": "Yakutat" + } + ] + }, + { + "code": "1837-4", + "display": "Tsimshian", + "definition": "Tsimshian", + "concept": [ + { + "code": "1838-2", + "display": "Metlakatla", + "definition": "Metlakatla" + } + ] + } + ] + } + ] + }, + { + "code": "1840-8", + "display": "Eskimo", + "definition": "Eskimo", + "concept": [ + { + "code": "1842-4", + "display": "Greenland Eskimo", + "definition": "Greenland Eskimo" + }, + { + "code": "1844-0", + "display": "Inupiat Eskimo", + "definition": "Inupiat Eskimo", + "concept": [ + { + "code": "1845-7", + "display": "Ambler", + "definition": "Ambler" + }, + { + "code": "1846-5", + "display": "Anaktuvuk", + "definition": "Anaktuvuk" + }, + { + "code": "1847-3", + "display": "Anaktuvuk Pass", + "definition": "Anaktuvuk Pass" + }, + { + "code": "1848-1", + "display": "Arctic Slope Inupiat", + "definition": "Arctic Slope Inupiat" + }, + { + "code": "1849-9", + "display": "Arctic Slope Corporation", + "definition": "Arctic Slope Corporation" + }, + { + "code": "1850-7", + "display": "Atqasuk", + "definition": "Atqasuk" + }, + { + "code": "1851-5", + "display": "Barrow", + "definition": "Barrow" + }, + { + "code": "1852-3", + "display": "Bering Straits Inupiat", + "definition": "Bering Straits Inupiat" + }, + { + "code": "1853-1", + "display": "Brevig Mission", + "definition": "Brevig Mission" + }, + { + "code": "1854-9", + "display": "Buckland", + "definition": "Buckland" + }, + { + "code": "1855-6", + "display": "Chinik", + "definition": "Chinik" + }, + { + "code": "1856-4", + "display": "Council", + "definition": "Council" + }, + { + "code": "1857-2", + "display": "Deering", + "definition": "Deering" + }, + { + "code": "1858-0", + "display": "Elim", + "definition": "Elim" + }, + { + "code": "1859-8", + "display": "Golovin", + "definition": "Golovin" + }, + { + "code": "1860-6", + "display": "Inalik Diomede", + "definition": "Inalik Diomede" + }, + { + "code": "1861-4", + "display": "Inupiaq", + "definition": "Inupiaq" + }, + { + "code": "1862-2", + "display": "Kaktovik", + "definition": "Kaktovik" + }, + { + "code": "1863-0", + "display": "Kawerak", + "definition": "Kawerak" + }, + { + "code": "1864-8", + "display": "Kiana", + "definition": "Kiana" + }, + { + "code": "1865-5", + "display": "Kivalina", + "definition": "Kivalina" + }, + { + "code": "1866-3", + "display": "Kobuk", + "definition": "Kobuk" + }, + { + "code": "1867-1", + "display": "Kotzebue", + "definition": "Kotzebue" + }, + { + "code": "1868-9", + "display": "Koyuk", + "definition": "Koyuk" + }, + { + "code": "1869-7", + "display": "Kwiguk", + "definition": "Kwiguk" + }, + { + "code": "1870-5", + "display": "Mauneluk Inupiat", + "definition": "Mauneluk Inupiat" + }, + { + "code": "1871-3", + "display": "Nana Inupiat", + "definition": "Nana Inupiat" + }, + { + "code": "1872-1", + "display": "Noatak", + "definition": "Noatak" + }, + { + "code": "1873-9", + "display": "Nome", + "definition": "Nome" + }, + { + "code": "1874-7", + "display": "Noorvik", + "definition": "Noorvik" + }, + { + "code": "1875-4", + "display": "Nuiqsut", + "definition": "Nuiqsut" + }, + { + "code": "1876-2", + "display": "Point Hope", + "definition": "Point Hope" + }, + { + "code": "1877-0", + "display": "Point Lay", + "definition": "Point Lay" + }, + { + "code": "1878-8", + "display": "Selawik", + "definition": "Selawik" + }, + { + "code": "1879-6", + "display": "Shaktoolik", + "definition": "Shaktoolik" + }, + { + "code": "1880-4", + "display": "Shishmaref", + "definition": "Shishmaref" + }, + { + "code": "1881-2", + "display": "Shungnak", + "definition": "Shungnak" + }, + { + "code": "1882-0", + "display": "Solomon", + "definition": "Solomon" + }, + { + "code": "1883-8", + "display": "Teller", + "definition": "Teller" + }, + { + "code": "1884-6", + "display": "Unalakleet", + "definition": "Unalakleet" + }, + { + "code": "1885-3", + "display": "Wainwright", + "definition": "Wainwright" + }, + { + "code": "1886-1", + "display": "Wales", + "definition": "Wales" + }, + { + "code": "1887-9", + "display": "White Mountain", + "definition": "White Mountain" + }, + { + "code": "1888-7", + "display": "White Mountain Inupiat", + "definition": "White Mountain Inupiat" + }, + { + "code": "1889-5", + "display": "Mary's Igloo", + "definition": "Mary's Igloo" + } + ] + }, + { + "code": "1891-1", + "display": "Siberian Eskimo", + "definition": "Siberian Eskimo", + "concept": [ + { + "code": "1892-9", + "display": "Gambell", + "definition": "Gambell" + }, + { + "code": "1893-7", + "display": "Savoonga", + "definition": "Savoonga" + }, + { + "code": "1894-5", + "display": "Siberian Yupik", + "definition": "Siberian Yupik" + } + ] + }, + { + "code": "1896-0", + "display": "Yupik Eskimo", + "definition": "Yupik Eskimo", + "concept": [ + { + "code": "1897-8", + "display": "Akiachak", + "definition": "Akiachak" + }, + { + "code": "1898-6", + "display": "Akiak", + "definition": "Akiak" + }, + { + "code": "1899-4", + "display": "Alakanuk", + "definition": "Alakanuk" + }, + { + "code": "1900-0", + "display": "Aleknagik", + "definition": "Aleknagik" + }, + { + "code": "1901-8", + "display": "Andreafsky", + "definition": "Andreafsky" + }, + { + "code": "1902-6", + "display": "Aniak", + "definition": "Aniak" + }, + { + "code": "1903-4", + "display": "Atmautluak", + "definition": "Atmautluak" + }, + { + "code": "1904-2", + "display": "Bethel", + "definition": "Bethel" + }, + { + "code": "1905-9", + "display": "Bill Moore's Slough", + "definition": "Bill Moore's Slough" + }, + { + "code": "1906-7", + "display": "Bristol Bay Yupik", + "definition": "Bristol Bay Yupik" + }, + { + "code": "1907-5", + "display": "Calista Yupik", + "definition": "Calista Yupik" + }, + { + "code": "1908-3", + "display": "Chefornak", + "definition": "Chefornak" + }, + { + "code": "1909-1", + "display": "Chevak", + "definition": "Chevak" + }, + { + "code": "1910-9", + "display": "Chuathbaluk", + "definition": "Chuathbaluk" + }, + { + "code": "1911-7", + "display": "Clark's Point", + "definition": "Clark's Point" + }, + { + "code": "1912-5", + "display": "Crooked Creek", + "definition": "Crooked Creek" + }, + { + "code": "1913-3", + "display": "Dillingham", + "definition": "Dillingham" + }, + { + "code": "1914-1", + "display": "Eek", + "definition": "Eek" + }, + { + "code": "1915-8", + "display": "Ekuk", + "definition": "Ekuk" + }, + { + "code": "1916-6", + "display": "Ekwok", + "definition": "Ekwok" + }, + { + "code": "1917-4", + "display": "Emmonak", + "definition": "Emmonak" + }, + { + "code": "1918-2", + "display": "Goodnews Bay", + "definition": "Goodnews Bay" + }, + { + "code": "1919-0", + "display": "Hooper Bay", + "definition": "Hooper Bay" + }, + { + "code": "1920-8", + "display": "Iqurmuit (Russian Mission)", + "definition": "Iqurmuit (Russian Mission)" + }, + { + "code": "1921-6", + "display": "Kalskag", + "definition": "Kalskag" + }, + { + "code": "1922-4", + "display": "Kasigluk", + "definition": "Kasigluk" + }, + { + "code": "1923-2", + "display": "Kipnuk", + "definition": "Kipnuk" + }, + { + "code": "1924-0", + "display": "Koliganek", + "definition": "Koliganek" + }, + { + "code": "1925-7", + "display": "Kongiganak", + "definition": "Kongiganak" + }, + { + "code": "1926-5", + "display": "Kotlik", + "definition": "Kotlik" + }, + { + "code": "1927-3", + "display": "Kwethluk", + "definition": "Kwethluk" + }, + { + "code": "1928-1", + "display": "Kwigillingok", + "definition": "Kwigillingok" + }, + { + "code": "1929-9", + "display": "Levelock", + "definition": "Levelock" + }, + { + "code": "1930-7", + "display": "Lower Kalskag", + "definition": "Lower Kalskag" + }, + { + "code": "1931-5", + "display": "Manokotak", + "definition": "Manokotak" + }, + { + "code": "1932-3", + "display": "Marshall", + "definition": "Marshall" + }, + { + "code": "1933-1", + "display": "Mekoryuk", + "definition": "Mekoryuk" + }, + { + "code": "1934-9", + "display": "Mountain Village", + "definition": "Mountain Village" + }, + { + "code": "1935-6", + "display": "Naknek", + "definition": "Naknek" + }, + { + "code": "1936-4", + "display": "Napaumute", + "definition": "Napaumute" + }, + { + "code": "1937-2", + "display": "Napakiak", + "definition": "Napakiak" + }, + { + "code": "1938-0", + "display": "Napaskiak", + "definition": "Napaskiak" + }, + { + "code": "1939-8", + "display": "Newhalen", + "definition": "Newhalen" + }, + { + "code": "1940-6", + "display": "New Stuyahok", + "definition": "New Stuyahok" + }, + { + "code": "1941-4", + "display": "Newtok", + "definition": "Newtok" + }, + { + "code": "1942-2", + "display": "Nightmute", + "definition": "Nightmute" + }, + { + "code": "1943-0", + "display": "Nunapitchukv", + "definition": "Nunapitchukv" + }, + { + "code": "1944-8", + "display": "Oscarville", + "definition": "Oscarville" + }, + { + "code": "1945-5", + "display": "Pilot Station", + "definition": "Pilot Station" + }, + { + "code": "1946-3", + "display": "Pitkas Point", + "definition": "Pitkas Point" + }, + { + "code": "1947-1", + "display": "Platinum", + "definition": "Platinum" + }, + { + "code": "1948-9", + "display": "Portage Creek", + "definition": "Portage Creek" + }, + { + "code": "1949-7", + "display": "Quinhagak", + "definition": "Quinhagak" + }, + { + "code": "1950-5", + "display": "Red Devil", + "definition": "Red Devil" + }, + { + "code": "1951-3", + "display": "St. Michael", + "definition": "St. Michael" + }, + { + "code": "1952-1", + "display": "Scammon Bay", + "definition": "Scammon Bay" + }, + { + "code": "1953-9", + "display": "Sheldon's Point", + "definition": "Sheldon's Point" + }, + { + "code": "1954-7", + "display": "Sleetmute", + "definition": "Sleetmute" + }, + { + "code": "1955-4", + "display": "Stebbins", + "definition": "Stebbins" + }, + { + "code": "1956-2", + "display": "Togiak", + "definition": "Togiak" + }, + { + "code": "1957-0", + "display": "Toksook", + "definition": "Toksook" + }, + { + "code": "1958-8", + "display": "Tulukskak", + "definition": "Tulukskak" + }, + { + "code": "1959-6", + "display": "Tuntutuliak", + "definition": "Tuntutuliak" + }, + { + "code": "1960-4", + "display": "Tununak", + "definition": "Tununak" + }, + { + "code": "1961-2", + "display": "Twin Hills", + "definition": "Twin Hills" + }, + { + "code": "1962-0", + "display": "Georgetown", + "definition": "Georgetown" + }, + { + "code": "1963-8", + "display": "St. Mary's", + "definition": "St. Mary's" + }, + { + "code": "1964-6", + "display": "Umkumiate", + "definition": "Umkumiate" + } + ] + } + ] + }, + { + "code": "1966-1", + "display": "Aleut", + "definition": "Aleut", + "concept": [ + { + "code": "1968-7", + "display": "Alutiiq Aleut", + "definition": "Alutiiq Aleut", + "concept": [ + { + "code": "1969-5", + "display": "Tatitlek", + "definition": "Tatitlek" + }, + { + "code": "1970-3", + "display": "Ugashik", + "definition": "Ugashik" + } + ] + }, + { + "code": "1972-9", + "display": "Bristol Bay Aleut", + "definition": "Bristol Bay Aleut", + "concept": [ + { + "code": "1973-7", + "display": "Chignik", + "definition": "Chignik" + }, + { + "code": "1974-5", + "display": "Chignik Lake", + "definition": "Chignik Lake" + }, + { + "code": "1975-2", + "display": "Egegik", + "definition": "Egegik" + }, + { + "code": "1976-0", + "display": "Igiugig", + "definition": "Igiugig" + }, + { + "code": "1977-8", + "display": "Ivanof Bay", + "definition": "Ivanof Bay" + }, + { + "code": "1978-6", + "display": "King Salmon", + "definition": "King Salmon" + }, + { + "code": "1979-4", + "display": "Kokhanok", + "definition": "Kokhanok" + }, + { + "code": "1980-2", + "display": "Perryville", + "definition": "Perryville" + }, + { + "code": "1981-0", + "display": "Pilot Point", + "definition": "Pilot Point" + }, + { + "code": "1982-8", + "display": "Port Heiden", + "definition": "Port Heiden" + } + ] + }, + { + "code": "1984-4", + "display": "Chugach Aleut", + "definition": "Chugach Aleut", + "concept": [ + { + "code": "1985-1", + "display": "Chenega", + "definition": "Chenega" + }, + { + "code": "1986-9", + "display": "Chugach Corporation", + "definition": "Chugach Corporation" + }, + { + "code": "1987-7", + "display": "English Bay", + "definition": "English Bay" + }, + { + "code": "1988-5", + "display": "Port Graham", + "definition": "Port Graham" + } + ] + }, + { + "code": "1990-1", + "display": "Eyak", + "definition": "Eyak" + }, + { + "code": "1992-7", + "display": "Koniag Aleut", + "definition": "Koniag Aleut", + "concept": [ + { + "code": "1993-5", + "display": "Akhiok", + "definition": "Akhiok" + }, + { + "code": "1994-3", + "display": "Agdaagux", + "definition": "Agdaagux" + }, + { + "code": "1995-0", + "display": "Karluk", + "definition": "Karluk" + }, + { + "code": "1996-8", + "display": "Kodiak", + "definition": "Kodiak" + }, + { + "code": "1997-6", + "display": "Larsen Bay", + "definition": "Larsen Bay" + }, + { + "code": "1998-4", + "display": "Old Harbor", + "definition": "Old Harbor" + }, + { + "code": "1999-2", + "display": "Ouzinkie", + "definition": "Ouzinkie" + }, + { + "code": "2000-8", + "display": "Port Lions", + "definition": "Port Lions" + } + ] + }, + { + "code": "2002-4", + "display": "Sugpiaq", + "definition": "Sugpiaq" + }, + { + "code": "2004-0", + "display": "Suqpigaq", + "definition": "Suqpigaq" + }, + { + "code": "2006-5", + "display": "Unangan Aleut", + "definition": "Unangan Aleut", + "concept": [ + { + "code": "2007-3", + "display": "Akutan", + "definition": "Akutan" + }, + { + "code": "2008-1", + "display": "Aleut Corporation", + "definition": "Aleut Corporation" + }, + { + "code": "2009-9", + "display": "Aleutian", + "definition": "Aleutian" + }, + { + "code": "2010-7", + "display": "Aleutian Islander", + "definition": "Aleutian Islander" + }, + { + "code": "2011-5", + "display": "Atka", + "definition": "Atka" + }, + { + "code": "2012-3", + "display": "Belkofski", + "definition": "Belkofski" + }, + { + "code": "2013-1", + "display": "Chignik Lagoon", + "definition": "Chignik Lagoon" + }, + { + "code": "2014-9", + "display": "King Cove", + "definition": "King Cove" + }, + { + "code": "2015-6", + "display": "False Pass", + "definition": "False Pass" + }, + { + "code": "2016-4", + "display": "Nelson Lagoon", + "definition": "Nelson Lagoon" + }, + { + "code": "2017-2", + "display": "Nikolski", + "definition": "Nikolski" + }, + { + "code": "2018-0", + "display": "Pauloff Harbor", + "definition": "Pauloff Harbor" + }, + { + "code": "2019-8", + "display": "Qagan Toyagungin", + "definition": "Qagan Toyagungin" + }, + { + "code": "2020-6", + "display": "Qawalangin", + "definition": "Qawalangin" + }, + { + "code": "2021-4", + "display": "St. George", + "definition": "St. George" + }, + { + "code": "2022-2", + "display": "St. Paul", + "definition": "St. Paul" + }, + { + "code": "2023-0", + "display": "Sand Point", + "definition": "Sand Point" + }, + { + "code": "2024-8", + "display": "South Naknek", + "definition": "South Naknek" + }, + { + "code": "2025-5", + "display": "Unalaska", + "definition": "Unalaska" + }, + { + "code": "2026-3", + "display": "Unga", + "definition": "Unga" + } + ] + } + ] + } + ] + } + ] + }, + { + "code": "2028-9", + "display": "Asian", + "definition": "Asian", + "concept": [ + { + "code": "2029-7", + "display": "Asian Indian", + "definition": "Asian Indian" + }, + { + "code": "2030-5", + "display": "Bangladeshi", + "definition": "Bangladeshi" + }, + { + "code": "2031-3", + "display": "Bhutanese", + "definition": "Bhutanese" + }, + { + "code": "2032-1", + "display": "Burmese", + "definition": "Burmese" + }, + { + "code": "2033-9", + "display": "Cambodian", + "definition": "Cambodian" + }, + { + "code": "2034-7", + "display": "Chinese", + "definition": "Chinese" + }, + { + "code": "2035-4", + "display": "Taiwanese", + "definition": "Taiwanese" + }, + { + "code": "2036-2", + "display": "Filipino", + "definition": "Filipino" + }, + { + "code": "2037-0", + "display": "Hmong", + "definition": "Hmong" + }, + { + "code": "2038-8", + "display": "Indonesian", + "definition": "Indonesian" + }, + { + "code": "2039-6", + "display": "Japanese", + "definition": "Japanese" + }, + { + "code": "2040-4", + "display": "Korean", + "definition": "Korean" + }, + { + "code": "2041-2", + "display": "Laotian", + "definition": "Laotian" + }, + { + "code": "2042-0", + "display": "Malaysian", + "definition": "Malaysian" + }, + { + "code": "2043-8", + "display": "Okinawan", + "definition": "Okinawan" + }, + { + "code": "2044-6", + "display": "Pakistani", + "definition": "Pakistani" + }, + { + "code": "2045-3", + "display": "Sri Lankan", + "definition": "Sri Lankan" + }, + { + "code": "2046-1", + "display": "Thai", + "definition": "Thai" + }, + { + "code": "2047-9", + "display": "Vietnamese", + "definition": "Vietnamese" + }, + { + "code": "2048-7", + "display": "Iwo Jiman", + "definition": "Iwo Jiman" + }, + { + "code": "2049-5", + "display": "Maldivian", + "definition": "Maldivian" + }, + { + "code": "2050-3", + "display": "Nepalese", + "definition": "Nepalese" + }, + { + "code": "2051-1", + "display": "Singaporean", + "definition": "Singaporean" + }, + { + "code": "2052-9", + "display": "Madagascar", + "definition": "Madagascar" + } + ] + }, + { + "code": "2054-5", + "display": "Black or African American", + "definition": "Black or African American", + "concept": [ + { + "code": "2056-0", + "display": "Black", + "definition": "Black" + }, + { + "code": "2058-6", + "display": "African American", + "definition": "African American" + }, + { + "code": "2060-2", + "display": "African", + "definition": "African", + "concept": [ + { + "code": "2061-0", + "display": "Botswanan", + "definition": "Botswanan" + }, + { + "code": "2062-8", + "display": "Ethiopian", + "definition": "Ethiopian" + }, + { + "code": "2063-6", + "display": "Liberian", + "definition": "Liberian" + }, + { + "code": "2064-4", + "display": "Namibian", + "definition": "Namibian" + }, + { + "code": "2065-1", + "display": "Nigerian", + "definition": "Nigerian" + }, + { + "code": "2066-9", + "display": "Zairean", + "definition": "Zairean" + } + ] + }, + { + "code": "2067-7", + "display": "Bahamian", + "definition": "Bahamian" + }, + { + "code": "2068-5", + "display": "Barbadian", + "definition": "Barbadian" + }, + { + "code": "2069-3", + "display": "Dominican", + "definition": "Dominican" + }, + { + "code": "2070-1", + "display": "Dominica Islander", + "definition": "Dominica Islander" + }, + { + "code": "2071-9", + "display": "Haitian", + "definition": "Haitian" + }, + { + "code": "2072-7", + "display": "Jamaican", + "definition": "Jamaican" + }, + { + "code": "2073-5", + "display": "Tobagoan", + "definition": "Tobagoan" + }, + { + "code": "2074-3", + "display": "Trinidadian", + "definition": "Trinidadian" + }, + { + "code": "2075-0", + "display": "West Indian", + "definition": "West Indian" + } + ] + }, + { + "code": "2076-8", + "display": "Native Hawaiian or Other Pacific Islander", + "definition": "Native Hawaiian or Other Pacific Islander", + "concept": [ + { + "code": "2078-4", + "display": "Polynesian", + "definition": "Polynesian", + "concept": [ + { + "code": "2079-2", + "display": "Native Hawaiian", + "definition": "Native Hawaiian" + }, + { + "code": "2080-0", + "display": "Samoan", + "definition": "Samoan" + }, + { + "code": "2081-8", + "display": "Tahitian", + "definition": "Tahitian" + }, + { + "code": "2082-6", + "display": "Tongan", + "definition": "Tongan" + }, + { + "code": "2083-4", + "display": "Tokelauan", + "definition": "Tokelauan" + } + ] + }, + { + "code": "2085-9", + "display": "Micronesian", + "definition": "Micronesian", + "concept": [ + { + "code": "2086-7", + "display": "Guamanian or Chamorro", + "definition": "Guamanian or Chamorro" + }, + { + "code": "2087-5", + "display": "Guamanian", + "definition": "Guamanian" + }, + { + "code": "2088-3", + "display": "Chamorro", + "definition": "Chamorro" + }, + { + "code": "2089-1", + "display": "Mariana Islander", + "definition": "Mariana Islander" + }, + { + "code": "2090-9", + "display": "Marshallese", + "definition": "Marshallese" + }, + { + "code": "2091-7", + "display": "Palauan", + "definition": "Palauan" + }, + { + "code": "2092-5", + "display": "Carolinian", + "definition": "Carolinian" + }, + { + "code": "2093-3", + "display": "Kosraean", + "definition": "Kosraean" + }, + { + "code": "2094-1", + "display": "Pohnpeian", + "definition": "Pohnpeian" + }, + { + "code": "2095-8", + "display": "Saipanese", + "definition": "Saipanese" + }, + { + "code": "2096-6", + "display": "Kiribati", + "definition": "Kiribati" + }, + { + "code": "2097-4", + "display": "Chuukese", + "definition": "Chuukese" + }, + { + "code": "2098-2", + "display": "Yapese", + "definition": "Yapese" + } + ] + }, + { + "code": "2100-6", + "display": "Melanesian", + "definition": "Melanesian", + "concept": [ + { + "code": "2101-4", + "display": "Fijian", + "definition": "Fijian" + }, + { + "code": "2102-2", + "display": "Papua New Guinean", + "definition": "Papua New Guinean" + }, + { + "code": "2103-0", + "display": "Solomon Islander", + "definition": "Solomon Islander" + }, + { + "code": "2104-8", + "display": "New Hebrides", + "definition": "New Hebrides" + } + ] + }, + { + "code": "2500-7", + "display": "Other Pacific Islander", + "definition": "Note that this term remains in the table for completeness, even though within HL7, the notion of Other code is deprecated." + } + ] + }, + { + "code": "2106-3", + "display": "White", + "definition": "White", + "concept": [ + { + "code": "2108-9", + "display": "European", + "definition": "European", + "concept": [ + { + "code": "2109-7", + "display": "Armenian", + "definition": "Armenian" + }, + { + "code": "2110-5", + "display": "English", + "definition": "English" + }, + { + "code": "2111-3", + "display": "French", + "definition": "French" + }, + { + "code": "2112-1", + "display": "German", + "definition": "German" + }, + { + "code": "2113-9", + "display": "Irish", + "definition": "Irish" + }, + { + "code": "2114-7", + "display": "Italian", + "definition": "Italian" + }, + { + "code": "2115-4", + "display": "Polish", + "definition": "Polish" + }, + { + "code": "2116-2", + "display": "Scottish", + "definition": "Scottish" + } + ] + }, + { + "code": "2118-8", + "display": "Middle Eastern or North African", + "definition": "Middle Eastern or North African", + "concept": [ + { + "code": "2119-6", + "display": "Assyrian", + "definition": "Assyrian" + }, + { + "code": "2120-4", + "display": "Egyptian", + "definition": "Egyptian" + }, + { + "code": "2121-2", + "display": "Iranian", + "definition": "Iranian" + }, + { + "code": "2122-0", + "display": "Iraqi", + "definition": "Iraqi" + }, + { + "code": "2123-8", + "display": "Lebanese", + "definition": "Lebanese" + }, + { + "code": "2124-6", + "display": "Palestinian", + "definition": "Palestinian" + }, + { + "code": "2125-3", + "display": "Syrian", + "definition": "Syrian" + }, + { + "code": "2126-1", + "display": "Afghanistani", + "definition": "Afghanistani" + }, + { + "code": "2127-9", + "display": "Israeili", + "definition": "Israeili" + } + ] + }, + { + "code": "2129-5", + "display": "Arab", + "definition": "Arab" + } + ] + }, + { + "code": "2131-1", + "display": "Other Race", + "definition": "Note that this term remains in the table for completeness, even though within HL7, the notion of Other code is deprecated." + } + ] +} \ No newline at end of file From a896e5b7f9805c803aa8861328f40ba8c3390c96 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Fri, 19 Apr 2024 12:09:15 -0700 Subject: [PATCH 115/143] The hl7.org value-sets are no longer available for automated downloads, requiring a captcha. Pulled the content to files on the file system. --- portal/models/clinical_constants.py | 19 +- .../models/code_systems/v3-Ethnicity.cs.json | 274 + portal/models/code_systems/v3-Race.cs.json | 4846 +++++++++++++++++ 3 files changed, 5136 insertions(+), 3 deletions(-) create mode 100644 portal/models/code_systems/v3-Ethnicity.cs.json create mode 100644 portal/models/code_systems/v3-Race.cs.json diff --git a/portal/models/clinical_constants.py b/portal/models/clinical_constants.py index 2df5892ac..502a8f5d3 100644 --- a/portal/models/clinical_constants.py +++ b/portal/models/clinical_constants.py @@ -1,4 +1,6 @@ """ TrueNTH Clinical Codes """ +import json +import os import requests from ..database import db @@ -90,12 +92,23 @@ def parse_concepts(elements, system): return ccs -def fetch_HL7_V3_Namespace(valueSet): +def fetch_HL7_V3_Namespace(valueSet, pull_from_hl7=False): """Pull and parse the published FHIR ethnicity namespace""" + # NB, this used to be pulled on every deploy, but hl7.org now requires human + # intervention, to bypass the captcha - now pulling cached version off file + # system. src_url = 'http://hl7.org/fhir/STU3/v3/{valueSet}/v3-{valueSet}.cs.json'.format( valueSet=valueSet) - response = requests.get(src_url) - return parse_concepts(response.json()['concept'], + if pull_from_hl7: + response = requests.get(src_url) + concept_source = response.json() + else: + cwd = os.path.dirname(__file__) + fp = os.path.join(cwd, f'code_systems/v3-{valueSet}.cs.json') + with open(fp, 'r') as jfile: + concept_source = json.load(jfile) + + return parse_concepts(concept_source['concept'], system='http://hl7.org/fhir/v3/{}'.format(valueSet)) diff --git a/portal/models/code_systems/v3-Ethnicity.cs.json b/portal/models/code_systems/v3-Ethnicity.cs.json new file mode 100644 index 000000000..780e8111b --- /dev/null +++ b/portal/models/code_systems/v3-Ethnicity.cs.json @@ -0,0 +1,274 @@ +{ + "resourceType": "CodeSystem", + "id": "v3-Ethnicity", + "meta": { + "lastUpdated": "2016-11-11T00:00:00.000+11:00" + }, + "text": { + "status": "generated", + "div": "

Release Date: 2016-11-11

\r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
LevelCodeDisplayDefinition
12135-2 Hispanic or Latino
2  2137-8 Spaniard
3    2138-6 Andalusian
3    2139-4 Asturian
3    2140-2 Castillian
3    2141-0 Catalonian
3    2142-8 Belearic Islander
3    2143-6 Gallego
3    2144-4 Valencian
3    2145-1 Canarian
3    2146-9 Spanish Basque
2  2148-5 Mexican
3    2149-3 Mexican American
3    2150-1 Mexicano
3    2151-9 Chicano
3    2152-7 La Raza
3    2153-5 Mexican American Indian
2  2155-0 Central American
3    2156-8 Costa Rican
3    2157-6 Guatemalan
3    2158-4 Honduran
3    2159-2 Nicaraguan
3    2160-0 Panamanian
3    2161-8 Salvadoran
3    2162-6 Central American Indian
3    2163-4 Canal Zone
2  2165-9 South American
3    2166-7 Argentinean
3    2167-5 Bolivian
3    2168-3 Chilean
3    2169-1 Colombian
3    2170-9 Ecuadorian
3    2171-7 Paraguayan
3    2172-5 Peruvian
3    2173-3 Uruguayan
3    2174-1 Venezuelan
3    2175-8 South American Indian
3    2176-6 Criollo
2  2178-2 Latin American
2  2180-8 Puerto Rican
2  2182-4 Cuban
2  2184-0 Dominican
12186-5 Not Hispanic or Latino\n Note that this term remains in the table for completeness, even though within HL7, the notion of "not otherwise coded" term is deprecated.
\r\n\n
\r\n
" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-ballot-status", + "valueString": "External" + }, + { + "url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-fmm", + "valueInteger": 0 + } + ], + "url": "http://hl7.org/fhir/v3/Ethnicity", + "identifier": { + "system": "urn:ietf:rfc:3986", + "value": "urn:oid:2.16.840.1.113883.5.50" + }, + "version": "2016-11-11", + "name": "v3 Code System Ethnicity", + "status": "active", + "experimental": false, + "date": "2016-11-11T00:00:00+11:00", + "publisher": "HL7, Inc", + "contact": [ + { + "telecom": [ + { + "system": "url", + "value": "http://hl7.org" + } + ] + } + ], + "description": " In the United States, federal standards for classifying data on ethnicity determine the categories used by federal agencies and exert a strong influence on categorization by state and local agencies and private sector organizations. The federal standards do not conceptually define ethnicity, and they recognize the absence of an anthropological or scientific basis for ethnicity classification. Instead, the federal standards acknowledge that ethnicity is a social-political construct in which an individual's own identification with a particular ethnicity is preferred to observer identification. The standards specify two minimum ethnicity categories: Hispanic or Latino, and Not Hispanic or Latino. The standards define a Hispanic or Latino as a person of \"Mexican, Puerto Rican, Cuban, South or Central America, or other Spanish culture or origin, regardless of race.\" The standards stipulate that ethnicity data need not be limited to the two minimum categories, but any expansion must be collapsible to those categories. In addition, the standards stipulate that an individual can be Hispanic or Latino or can be Not Hispanic or Latino, but cannot be both.", + "caseSensitive": true, + "valueSet": "http://hl7.org/fhir/ValueSet/v3-Ethnicity", + "hierarchyMeaning": "is-a", + "content": "complete", + "concept": [ + { + "code": "2135-2", + "display": "Hispanic or Latino", + "definition": "Hispanic or Latino", + "concept": [ + { + "code": "2137-8", + "display": "Spaniard", + "definition": "Spaniard", + "concept": [ + { + "code": "2138-6", + "display": "Andalusian", + "definition": "Andalusian" + }, + { + "code": "2139-4", + "display": "Asturian", + "definition": "Asturian" + }, + { + "code": "2140-2", + "display": "Castillian", + "definition": "Castillian" + }, + { + "code": "2141-0", + "display": "Catalonian", + "definition": "Catalonian" + }, + { + "code": "2142-8", + "display": "Belearic Islander", + "definition": "Belearic Islander" + }, + { + "code": "2143-6", + "display": "Gallego", + "definition": "Gallego" + }, + { + "code": "2144-4", + "display": "Valencian", + "definition": "Valencian" + }, + { + "code": "2145-1", + "display": "Canarian", + "definition": "Canarian" + }, + { + "code": "2146-9", + "display": "Spanish Basque", + "definition": "Spanish Basque" + } + ] + }, + { + "code": "2148-5", + "display": "Mexican", + "definition": "Mexican", + "concept": [ + { + "code": "2149-3", + "display": "Mexican American", + "definition": "Mexican American" + }, + { + "code": "2150-1", + "display": "Mexicano", + "definition": "Mexicano" + }, + { + "code": "2151-9", + "display": "Chicano", + "definition": "Chicano" + }, + { + "code": "2152-7", + "display": "La Raza", + "definition": "La Raza" + }, + { + "code": "2153-5", + "display": "Mexican American Indian", + "definition": "Mexican American Indian" + } + ] + }, + { + "code": "2155-0", + "display": "Central American", + "definition": "Central American", + "concept": [ + { + "code": "2156-8", + "display": "Costa Rican", + "definition": "Costa Rican" + }, + { + "code": "2157-6", + "display": "Guatemalan", + "definition": "Guatemalan" + }, + { + "code": "2158-4", + "display": "Honduran", + "definition": "Honduran" + }, + { + "code": "2159-2", + "display": "Nicaraguan", + "definition": "Nicaraguan" + }, + { + "code": "2160-0", + "display": "Panamanian", + "definition": "Panamanian" + }, + { + "code": "2161-8", + "display": "Salvadoran", + "definition": "Salvadoran" + }, + { + "code": "2162-6", + "display": "Central American Indian", + "definition": "Central American Indian" + }, + { + "code": "2163-4", + "display": "Canal Zone", + "definition": "Canal Zone" + } + ] + }, + { + "code": "2165-9", + "display": "South American", + "definition": "South American", + "concept": [ + { + "code": "2166-7", + "display": "Argentinean", + "definition": "Argentinean" + }, + { + "code": "2167-5", + "display": "Bolivian", + "definition": "Bolivian" + }, + { + "code": "2168-3", + "display": "Chilean", + "definition": "Chilean" + }, + { + "code": "2169-1", + "display": "Colombian", + "definition": "Colombian" + }, + { + "code": "2170-9", + "display": "Ecuadorian", + "definition": "Ecuadorian" + }, + { + "code": "2171-7", + "display": "Paraguayan", + "definition": "Paraguayan" + }, + { + "code": "2172-5", + "display": "Peruvian", + "definition": "Peruvian" + }, + { + "code": "2173-3", + "display": "Uruguayan", + "definition": "Uruguayan" + }, + { + "code": "2174-1", + "display": "Venezuelan", + "definition": "Venezuelan" + }, + { + "code": "2175-8", + "display": "South American Indian", + "definition": "South American Indian" + }, + { + "code": "2176-6", + "display": "Criollo", + "definition": "Criollo" + } + ] + }, + { + "code": "2178-2", + "display": "Latin American", + "definition": "Latin American" + }, + { + "code": "2180-8", + "display": "Puerto Rican", + "definition": "Puerto Rican" + }, + { + "code": "2182-4", + "display": "Cuban", + "definition": "Cuban" + }, + { + "code": "2184-0", + "display": "Dominican", + "definition": "Dominican" + } + ] + }, + { + "code": "2186-5", + "display": "Not Hispanic or Latino", + "definition": "Note that this term remains in the table for completeness, even though within HL7, the notion of \"not otherwise coded\" term is deprecated." + } + ] +} \ No newline at end of file diff --git a/portal/models/code_systems/v3-Race.cs.json b/portal/models/code_systems/v3-Race.cs.json new file mode 100644 index 000000000..9d854f17c --- /dev/null +++ b/portal/models/code_systems/v3-Race.cs.json @@ -0,0 +1,4846 @@ +{ + "resourceType": "CodeSystem", + "id": "v3-Race", + "meta": { + "lastUpdated": "2016-11-11T00:00:00.000+11:00" + }, + "text": { + "status": "generated", + "div": "

Release Date: 2016-11-11

\r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
LevelCodeDisplayDefinition
11002-5 American Indian or Alaska Native
2  1004-1 American Indian
3    1006-6 Abenaki
3    1008-2 Algonquian
3    1010-8 Apache
4      1011-6 Chiricahua
4      1012-4 Fort Sill Apache
4      1013-2 Jicarilla Apache
4      1014-0 Lipan Apache
4      1015-7 Mescalero Apache
4      1016-5 Oklahoma Apache
4      1017-3 Payson Apache
4      1018-1 San Carlos Apache
4      1019-9 White Mountain Apache
3    1021-5 Arapaho
4      1022-3 Northern Arapaho
4      1023-1 Southern Arapaho
4      1024-9 Wind River Arapaho
3    1026-4 Arikara
3    1028-0 Assiniboine
3    1030-6 Assiniboine Sioux
4      1031-4 Fort Peck Assiniboine Sioux
3    1033-0 Bannock
3    1035-5 Blackfeet
3    1037-1 Brotherton
3    1039-7 Burt Lake Band
3    1041-3 Caddo
4      1042-1 Oklahoma Cado
3    1044-7 Cahuilla
4      1045-4 Agua Caliente Cahuilla
4      1046-2 Augustine
4      1047-0 Cabazon
4      1048-8 Los Coyotes
4      1049-6 Morongo
4      1050-4 Santa Rosa Cahuilla
4      1051-2 Torres-Martinez
3    1053-8 California Tribes
4      1054-6 Cahto
4      1055-3 Chimariko
4      1056-1 Coast Miwok
4      1057-9 Digger
4      1058-7 Kawaiisu
4      1059-5 Kern River
4      1060-3 Mattole
4      1061-1 Red Wood
4      1062-9 Santa Rosa
4      1063-7 Takelma
4      1064-5 Wappo
4      1065-2 Yana
4      1066-0 Yuki
3    1068-6 Canadian and Latin American Indian
4      1069-4 Canadian Indian
4      1070-2 Central American Indian
4      1071-0 French American Indian
4      1072-8 Mexican American Indian
4      1073-6 South American Indian
3    1074-4 Spanish American Indian
3    1076-9 Catawba
4      1741-8 Alatna
4      1742-6 Alexander
4      1743-4 Allakaket
4      1744-2 Alanvik
4      1745-9 Anvik
4      1746-7 Arctic
4      1747-5 Beaver
4      1748-3 Birch Creek
4      1749-1 Cantwell
4      1750-9 Chalkyitsik
4      1751-7 Chickaloon
4      1752-5 Chistochina
4      1753-3 Chitina
4      1754-1 Circle
4      1755-8 Cook Inlet
4      1756-6 Copper Center
4      1757-4 Copper River
4      1758-2 Dot Lake
4      1759-0 Doyon
4      1760-8 Eagle
4      1761-6 Eklutna
4      1762-4 Evansville
4      1763-2 Fort Yukon
4      1764-0 Gakona
4      1765-7 Galena
4      1766-5 Grayling
4      1767-3 Gulkana
4      1768-1 Healy Lake
4      1769-9 Holy Cross
4      1770-7 Hughes
4      1771-5 Huslia
4      1772-3 Iliamna
4      1773-1 Kaltag
4      1774-9 Kluti Kaah
4      1775-6 Knik
4      1776-4 Koyukuk
4      1777-2 Lake Minchumina
4      1778-0 Lime
4      1779-8 Mcgrath
4      1780-6 Manley Hot Springs
4      1781-4 Mentasta Lake
4      1782-2 Minto
4      1783-0 Nenana
4      1784-8 Nikolai
4      1785-5 Ninilchik
4      1786-3 Nondalton
4      1787-1 Northway
4      1788-9 Nulato
4      1789-7 Pedro Bay
4      1790-5 Rampart
4      1791-3 Ruby
4      1792-1 Salamatof
4      1793-9 Seldovia
4      1794-7 Slana
4      1795-4 Shageluk
4      1796-2 Stevens
4      1797-0 Stony River
4      1798-8 Takotna
4      1799-6 Tanacross
4      1800-2 Tanaina
4      1801-0 Tanana
4      1802-8 Tanana Chiefs
4      1803-6 Tazlina
4      1804-4 Telida
4      1805-1 Tetlin
4      1806-9 Tok
4      1807-7 Tyonek
4      1808-5 Venetie
4      1809-3 Wiseman
3    1078-5 Cayuse
3    1080-1 Chehalis
3    1082-7 Chemakuan
4      1083-5 Hoh
4      1084-3 Quileute
3    1086-8 Chemehuevi
3    1088-4 Cherokee
4      1089-2 Cherokee Alabama
4      1090-0 Cherokees of Northeast Alabama
4      1091-8 Cherokees of Southeast Alabama
4      1092-6 Eastern Cherokee
4      1093-4 Echota Cherokee
4      1094-2 Etowah Cherokee
4      1095-9 Northern Cherokee
4      1096-7 Tuscola
4      1097-5 United Keetowah Band of Cherokee
4      1098-3 Western Cherokee
3    1100-7 Cherokee Shawnee
3    1102-3 Cheyenne
4      1103-1 Northern Cheyenne
4      1104-9 Southern Cheyenne
3    1106-4 Cheyenne-Arapaho
3    1108-0 Chickahominy
4      1109-8 Eastern Chickahominy
4      1110-6 Western Chickahominy
3    1112-2 Chickasaw
3    1114-8 Chinook
4      1115-5 Clatsop
4      1116-3 Columbia River Chinook
4      1117-1 Kathlamet
4      1118-9 Upper Chinook
4      1119-7 Wakiakum Chinook
4      1120-5 Willapa Chinook
4      1121-3 Wishram
3    1123-9 Chippewa
4      1124-7 Bad River
4      1125-4 Bay Mills Chippewa
4      1126-2 Bois Forte
4      1127-0 Burt Lake Chippewa
4      1128-8 Fond du Lac
4      1129-6 Grand Portage
4      1130-4 Grand Traverse Band of Ottawa-Chippewa
4      1131-2 Keweenaw
4      1132-0 Lac Courte Oreilles
4      1133-8 Lac du Flambeau
4      1134-6 Lac Vieux Desert Chippewa
4      1135-3 Lake Superior
4      1136-1 Leech Lake
4      1137-9 Little Shell Chippewa
4      1138-7 Mille Lacs
4      1139-5 Minnesota Chippewa
4      1140-3 Ontonagon
4      1141-1 Red Cliff Chippewa
4      1142-9 Red Lake Chippewa
4      1143-7 Saginaw Chippewa
4      1144-5 St. Croix Chippewa
4      1145-2 Sault Ste. Marie Chippewa
4      1146-0 Sokoagon Chippewa
4      1147-8 Turtle Mountain
4      1148-6 White Earth
3    1150-2 Chippewa Cree
4      1151-0 Rocky Boy's Chippewa Cree
3    1153-6 Chitimacha
3    1155-1 Choctaw
4      1156-9 Clifton Choctaw
4      1157-7 Jena Choctaw
4      1158-5 Mississippi Choctaw
4      1159-3 Mowa Band of Choctaw
4      1160-1 Oklahoma Choctaw
3    1162-7 Chumash
4      1163-5 Santa Ynez
3    1165-0 Clear Lake
3    1167-6 Coeur D'Alene
3    1169-2 Coharie
3    1171-8 Colorado River
3    1173-4 Colville
3    1175-9 Comanche
4      1176-7 Oklahoma Comanche
3    1178-3 Coos, Lower Umpqua, Siuslaw
3    1180-9 Coos
3    1182-5 Coquilles
3    1184-1 Costanoan
3    1186-6 Coushatta
4      1187-4 Alabama Coushatta
3    1189-0 Cowlitz
3    1191-6 Cree
3    1193-2 Creek
4      1194-0 Alabama Creek
4      1195-7 Alabama Quassarte
4      1196-5 Eastern Creek
4      1197-3 Eastern Muscogee
4      1198-1 Kialegee
4      1199-9 Lower Muscogee
4      1200-5 Machis Lower Creek Indian
4      1201-3 Poarch Band
4      1202-1 Principal Creek Indian Nation
4      1203-9 Star Clan of Muscogee Creeks
4      1204-7 Thlopthlocco
4      1205-4 Tuckabachee
3    1207-0 Croatan
3    1209-6 Crow
3    1211-2 Cupeno
4      1212-0 Agua Caliente
3    1214-6 Delaware
4      1215-3 Eastern Delaware
4      1216-1 Lenni-Lenape
4      1217-9 Munsee
4      1218-7 Oklahoma Delaware
4      1219-5 Rampough Mountain
4      1220-3 Sand Hill
3    1222-9 Diegueno
4      1223-7 Campo
4      1224-5 Capitan Grande
4      1225-2 Cuyapaipe
4      1226-0 La Posta
4      1227-8 Manzanita
4      1228-6 Mesa Grande
4      1229-4 San Pasqual
4      1230-2 Santa Ysabel
4      1231-0 Sycuan
3    1233-6 Eastern Tribes
4      1234-4 Attacapa
4      1235-1 Biloxi
4      1236-9 Georgetown
4      1237-7 Moor
4      1238-5 Nansemond
4      1239-3 Natchez
4      1240-1 Nausu Waiwash
4      1241-9 Nipmuc
4      1242-7 Paugussett
4      1243-5 Pocomoke Acohonock
4      1244-3 Southeastern Indians
4      1245-0 Susquehanock
4      1246-8 Tunica Biloxi
4      1247-6 Waccamaw-Siousan
4      1248-4 Wicomico
3    1250-0 Esselen
3    1252-6 Fort Belknap
3    1254-2 Fort Berthold
3    1256-7 Fort Mcdowell
3    1258-3 Fort Hall
3    1260-9 Gabrieleno
3    1262-5 Grand Ronde
3    1264-1 Gros Ventres
4      1265-8 Atsina
3    1267-4 Haliwa
3    1269-0 Hidatsa
3    1271-6 Hoopa
4      1272-4 Trinity
4      1273-2 Whilkut
3    1275-7 Hoopa Extension
3    1277-3 Houma
3    1279-9 Inaja-Cosmit
3    1281-5 Iowa
4      1282-3 Iowa of Kansas-Nebraska
4      1283-1 Iowa of Oklahoma
3    1285-6 Iroquois
4      1286-4 Cayuga
4      1287-2 Mohawk
4      1288-0 Oneida
4      1289-8 Onondaga
4      1290-6 Seneca
4      1291-4 Seneca Nation
4      1292-2 Seneca-Cayuga
4      1293-0 Tonawanda Seneca
4      1294-8 Tuscarora
4      1295-5 Wyandotte
3    1297-1 Juaneno
3    1299-7 Kalispel
3    1301-1 Karuk
3    1303-7 Kaw
3    1305-2 Kickapoo
4      1306-0 Oklahoma Kickapoo
4      1307-8 Texas Kickapoo
3    1309-4 Kiowa
4      1310-2 Oklahoma Kiowa
3    1312-8 Klallam
4      1313-6 Jamestown
4      1314-4 Lower Elwha
4      1315-1 Port Gamble Klallam
3    1317-7 Klamath
3    1319-3 Konkow
3    1321-9 Kootenai
3    1323-5 Lassik
3    1325-0 Long Island
4      1326-8 Matinecock
4      1327-6 Montauk
4      1328-4 Poospatuck
4      1329-2 Setauket
3    1331-8 Luiseno
4      1332-6 La Jolla
4      1333-4 Pala
4      1334-2 Pauma
4      1335-9 Pechanga
4      1336-7 Soboba
4      1337-5 Twenty-Nine Palms
4      1338-3 Temecula
3    1340-9 Lumbee
3    1342-5 Lummi
3    1344-1 Maidu
4      1345-8 Mountain Maidu
4      1346-6 Nishinam
3    1348-2 Makah
3    1350-8 Maliseet
3    1352-4 Mandan
3    1354-0 Mattaponi
3    1356-5 Menominee
3    1358-1 Miami
4      1359-9 Illinois Miami
4      1360-7 Indiana Miami
4      1361-5 Oklahoma Miami
3    1363-1 Miccosukee
3    1365-6 Micmac
4      1366-4 Aroostook
3    1368-0 Mission Indians
3    1370-6 Miwok
3    1372-2 Modoc
3    1374-8 Mohegan
3    1376-3 Mono
3    1378-9 Nanticoke
3    1380-5 Narragansett
3    1382-1 Navajo
4      1383-9 Alamo Navajo
4      1384-7 Canoncito Navajo
4      1385-4 Ramah Navajo
3    1387-0 Nez Perce
3    1389-6 Nomalaki
3    1391-2 Northwest Tribes
4      1392-0 Alsea
4      1393-8 Celilo
4      1394-6 Columbia
4      1395-3 Kalapuya
4      1396-1 Molala
4      1397-9 Talakamish
4      1398-7 Tenino
4      1399-5 Tillamook
4      1400-1 Wenatchee
4      1401-9 Yahooskin
3    1403-5 Omaha
3    1405-0 Oregon Athabaskan
3    1407-6 Osage
3    1409-2 Otoe-Missouria
3    1411-8 Ottawa
4      1412-6 Burt Lake Ottawa
4      1413-4 Michigan Ottawa
4      1414-2 Oklahoma Ottawa
3    1416-7 Paiute
4      1417-5 Bishop
4      1418-3 Bridgeport
4      1419-1 Burns Paiute
4      1420-9 Cedarville
4      1421-7 Fort Bidwell
4      1422-5 Fort Independence
4      1423-3 Kaibab
4      1424-1 Las Vegas
4      1425-8 Lone Pine
4      1426-6 Lovelock
4      1427-4 Malheur Paiute
4      1428-2 Moapa
4      1429-0 Northern Paiute
4      1430-8 Owens Valley
4      1431-6 Pyramid Lake
4      1432-4 San Juan Southern Paiute
4      1433-2 Southern Paiute
4      1434-0 Summit Lake
4      1435-7 Utu Utu Gwaitu Paiute
4      1436-5 Walker River
4      1437-3 Yerington Paiute
3    1439-9 Pamunkey
3    1441-5 Passamaquoddy
4      1442-3 Indian Township
4      1443-1 Pleasant Point Passamaquoddy
3    1445-6 Pawnee
4      1446-4 Oklahoma Pawnee
3    1448-0 Penobscot
3    1450-6 Peoria
4      1451-4 Oklahoma Peoria
3    1453-0 Pequot
4      1454-8 Marshantucket Pequot
3    1456-3 Pima
4      1457-1 Gila River Pima-Maricopa
4      1458-9 Salt River Pima-Maricopa
3    1460-5 Piscataway
3    1462-1 Pit River
3    1464-7 Pomo
4      1465-4 Central Pomo
4      1466-2 Dry Creek
4      1467-0 Eastern Pomo
4      1468-8 Kashia
4      1469-6 Northern Pomo
4      1470-4 Scotts Valley
4      1471-2 Stonyford
4      1472-0 Sulphur Bank
3    1474-6 Ponca
4      1475-3 Nebraska Ponca
4      1476-1 Oklahoma Ponca
3    1478-7 Potawatomi
4      1479-5 Citizen Band Potawatomi
4      1480-3 Forest County
4      1481-1 Hannahville
4      1482-9 Huron Potawatomi
4      1483-7 Pokagon Potawatomi
4      1484-5 Prairie Band
4      1485-2 Wisconsin Potawatomi
3    1487-8 Powhatan
3    1489-4 Pueblo
4      1490-2 Acoma
4      1491-0 Arizona Tewa
4      1492-8 Cochiti
4      1493-6 Hopi
4      1494-4 Isleta
4      1495-1 Jemez
4      1496-9 Keres
4      1497-7 Laguna
4      1498-5 Nambe
4      1499-3 Picuris
4      1500-8 Piro
4      1501-6 Pojoaque
4      1502-4 San Felipe
4      1503-2 San Ildefonso
4      1504-0 San Juan Pueblo
4      1505-7 San Juan De
4      1506-5 San Juan
4      1507-3 Sandia
4      1508-1 Santa Ana
4      1509-9 Santa Clara
4      1510-7 Santo Domingo
4      1511-5 Taos
4      1512-3 Tesuque
4      1513-1 Tewa
4      1514-9 Tigua
4      1515-6 Zia
4      1516-4 Zuni
3    1518-0 Puget Sound Salish
4      1519-8 Duwamish
4      1520-6 Kikiallus
4      1521-4 Lower Skagit
4      1522-2 Muckleshoot
4      1523-0 Nisqually
4      1524-8 Nooksack
4      1525-5 Port Madison
4      1526-3 Puyallup
4      1527-1 Samish
4      1528-9 Sauk-Suiattle
4      1529-7 Skokomish
4      1530-5 Skykomish
4      1531-3 Snohomish
4      1532-1 Snoqualmie
4      1533-9 Squaxin Island
4      1534-7 Steilacoom
4      1535-4 Stillaguamish
4      1536-2 Suquamish
4      1537-0 Swinomish
4      1538-8 Tulalip
4      1539-6 Upper Skagit
3    1541-2 Quapaw
3    1543-8 Quinault
3    1545-3 Rappahannock
3    1547-9 Reno-Sparks
3    1549-5 Round Valley
3    1551-1 Sac and Fox
4      1552-9 Iowa Sac and Fox
4      1553-7 Missouri Sac and Fox
4      1554-5 Oklahoma Sac and Fox
3    1556-0 Salinan
3    1558-6 Salish
3    1560-2 Salish and Kootenai
3    1562-8 Schaghticoke
3    1564-4 Scott Valley
3    1566-9 Seminole
4      1567-7 Big Cypress
4      1568-5 Brighton
4      1569-3 Florida Seminole
4      1570-1 Hollywood Seminole
4      1571-9 Oklahoma Seminole
3    1573-5 Serrano
4      1574-3 San Manual
3    1576-8 Shasta
3    1578-4 Shawnee
4      1579-2 Absentee Shawnee
4      1580-0 Eastern Shawnee
3    1582-6 Shinnecock
3    1584-2 Shoalwater Bay
3    1586-7 Shoshone
4      1587-5 Battle Mountain
4      1588-3 Duckwater
4      1589-1 Elko
4      1590-9 Ely
4      1591-7 Goshute
4      1592-5 Panamint
4      1593-3 Ruby Valley
4      1594-1 Skull Valley
4      1595-8 South Fork Shoshone
4      1596-6 Te-Moak Western Shoshone
4      1597-4 Timbi-Sha Shoshone
4      1598-2 Washakie
4      1599-0 Wind River Shoshone
4      1600-6 Yomba
3    1602-2 Shoshone Paiute
4      1603-0 Duck Valley
4      1604-8 Fallon
4      1605-5 Fort McDermitt
3    1607-1 Siletz
3    1609-7 Sioux
4      1610-5 Blackfoot Sioux
4      1611-3 Brule Sioux
4      1612-1 Cheyenne River Sioux
4      1613-9 Crow Creek Sioux
4      1614-7 Dakota Sioux
4      1615-4 Flandreau Santee
4      1616-2 Fort Peck
4      1617-0 Lake Traverse Sioux
4      1618-8 Lower Brule Sioux
4      1619-6 Lower Sioux
4      1620-4 Mdewakanton Sioux
4      1621-2 Miniconjou
4      1622-0 Oglala Sioux
4      1623-8 Pine Ridge Sioux
4      1624-6 Pipestone Sioux
4      1625-3 Prairie Island Sioux
4      1626-1 Prior Lake Sioux
4      1627-9 Rosebud Sioux
4      1628-7 Sans Arc Sioux
4      1629-5 Santee Sioux
4      1630-3 Sisseton-Wahpeton
4      1631-1 Sisseton Sioux
4      1632-9 Spirit Lake Sioux
4      1633-7 Standing Rock Sioux
4      1634-5 Teton Sioux
4      1635-2 Two Kettle Sioux
4      1636-0 Upper Sioux
4      1637-8 Wahpekute Sioux
4      1638-6 Wahpeton Sioux
4      1639-4 Wazhaza Sioux
4      1640-2 Yankton Sioux
4      1641-0 Yanktonai Sioux
3    1643-6 Siuslaw
3    1645-1 Spokane
3    1647-7 Stewart
3    1649-3 Stockbridge
3    1651-9 Susanville
3    1653-5 Tohono O'Odham
4      1654-3 Ak-Chin
4      1655-0 Gila Bend
4      1656-8 San Xavier
4      1657-6 Sells
3    1659-2 Tolowa
3    1661-8 Tonkawa
3    1663-4 Tygh
3    1665-9 Umatilla
3    1667-5 Umpqua
4      1668-3 Cow Creek Umpqua
3    1670-9 Ute
4      1671-7 Allen Canyon
4      1672-5 Uintah Ute
4      1673-3 Ute Mountain Ute
3    1675-8 Wailaki
3    1677-4 Walla-Walla
3    1679-0 Wampanoag
4      1680-8 Gay Head Wampanoag
4      1681-6 Mashpee Wampanoag
3    1683-2 Warm Springs
3    1685-7 Wascopum
3    1687-3 Washoe
4      1688-1 Alpine
4      1689-9 Carson
4      1690-7 Dresslerville
3    1692-3 Wichita
3    1694-9 Wind River
3    1696-4 Winnebago
4      1697-2 Ho-chunk
4      1698-0 Nebraska Winnebago
3    1700-4 Winnemucca
3    1702-0 Wintun
3    1704-6 Wiyot
4      1705-3 Table Bluff
3    1707-9 Yakama
3    1709-5 Yakama Cowlitz
3    1711-1 Yaqui
4      1712-9 Barrio Libre
4      1713-7 Pascua Yaqui
3    1715-2 Yavapai Apache
3    1717-8 Yokuts
4      1718-6 Chukchansi
4      1719-4 Tachi
4      1720-2 Tule River
3    1722-8 Yuchi
3    1724-4 Yuman
4      1725-1 Cocopah
4      1726-9 Havasupai
4      1727-7 Hualapai
4      1728-5 Maricopa
4      1729-3 Mohave
4      1730-1 Quechan
4      1731-9 Yavapai
3    1732-7 Yurok
4      1733-5 Coast Yurok
2  1735-0 Alaska Native
3    1737-6 Alaska Indian
4      1739-2 Alaskan Athabascan
5        1740-0 Ahtna
4      1811-9 Southeast Alaska
5        1813-5 Tlingit-Haida
6          1814-3 Angoon
6          1815-0 Central Council of Tlingit and Haida Tribes
6          1816-8 Chilkat
6          1817-6 Chilkoot
6          1818-4 Craig
6          1819-2 Douglas
6          1820-0 Haida
6          1821-8 Hoonah
6          1822-6 Hydaburg
6          1823-4 Kake
6          1824-2 Kasaan
6          1825-9 Kenaitze
6          1826-7 Ketchikan
6          1827-5 Klawock
6          1828-3 Pelican
6          1829-1 Petersburg
6          1830-9 Saxman
6          1831-7 Sitka
6          1832-5 Tenakee Springs
6          1833-3 Tlingit
6          1834-1 Wrangell
6          1835-8 Yakutat
5        1837-4 Tsimshian
6          1838-2 Metlakatla
3    1840-8 Eskimo
4      1842-4 Greenland Eskimo
4      1844-0 Inupiat Eskimo
5        1845-7 Ambler
5        1846-5 Anaktuvuk
5        1847-3 Anaktuvuk Pass
5        1848-1 Arctic Slope Inupiat
5        1849-9 Arctic Slope Corporation
5        1850-7 Atqasuk
5        1851-5 Barrow
5        1852-3 Bering Straits Inupiat
5        1853-1 Brevig Mission
5        1854-9 Buckland
5        1855-6 Chinik
5        1856-4 Council
5        1857-2 Deering
5        1858-0 Elim
5        1859-8 Golovin
5        1860-6 Inalik Diomede
5        1861-4 Inupiaq
5        1862-2 Kaktovik
5        1863-0 Kawerak
5        1864-8 Kiana
5        1865-5 Kivalina
5        1866-3 Kobuk
5        1867-1 Kotzebue
5        1868-9 Koyuk
5        1869-7 Kwiguk
5        1870-5 Mauneluk Inupiat
5        1871-3 Nana Inupiat
5        1872-1 Noatak
5        1873-9 Nome
5        1874-7 Noorvik
5        1875-4 Nuiqsut
5        1876-2 Point Hope
5        1877-0 Point Lay
5        1878-8 Selawik
5        1879-6 Shaktoolik
5        1880-4 Shishmaref
5        1881-2 Shungnak
5        1882-0 Solomon
5        1883-8 Teller
5        1884-6 Unalakleet
5        1885-3 Wainwright
5        1886-1 Wales
5        1887-9 White Mountain
5        1888-7 White Mountain Inupiat
5        1889-5 Mary's Igloo
4      1891-1 Siberian Eskimo
5        1892-9 Gambell
5        1893-7 Savoonga
5        1894-5 Siberian Yupik
4      1896-0 Yupik Eskimo
5        1897-8 Akiachak
5        1898-6 Akiak
5        1899-4 Alakanuk
5        1900-0 Aleknagik
5        1901-8 Andreafsky
5        1902-6 Aniak
5        1903-4 Atmautluak
5        1904-2 Bethel
5        1905-9 Bill Moore's Slough
5        1906-7 Bristol Bay Yupik
5        1907-5 Calista Yupik
5        1908-3 Chefornak
5        1909-1 Chevak
5        1910-9 Chuathbaluk
5        1911-7 Clark's Point
5        1912-5 Crooked Creek
5        1913-3 Dillingham
5        1914-1 Eek
5        1915-8 Ekuk
5        1916-6 Ekwok
5        1917-4 Emmonak
5        1918-2 Goodnews Bay
5        1919-0 Hooper Bay
5        1920-8 Iqurmuit (Russian Mission)
5        1921-6 Kalskag
5        1922-4 Kasigluk
5        1923-2 Kipnuk
5        1924-0 Koliganek
5        1925-7 Kongiganak
5        1926-5 Kotlik
5        1927-3 Kwethluk
5        1928-1 Kwigillingok
5        1929-9 Levelock
5        1930-7 Lower Kalskag
5        1931-5 Manokotak
5        1932-3 Marshall
5        1933-1 Mekoryuk
5        1934-9 Mountain Village
5        1935-6 Naknek
5        1936-4 Napaumute
5        1937-2 Napakiak
5        1938-0 Napaskiak
5        1939-8 Newhalen
5        1940-6 New Stuyahok
5        1941-4 Newtok
5        1942-2 Nightmute
5        1943-0 Nunapitchukv
5        1944-8 Oscarville
5        1945-5 Pilot Station
5        1946-3 Pitkas Point
5        1947-1 Platinum
5        1948-9 Portage Creek
5        1949-7 Quinhagak
5        1950-5 Red Devil
5        1951-3 St. Michael
5        1952-1 Scammon Bay
5        1953-9 Sheldon's Point
5        1954-7 Sleetmute
5        1955-4 Stebbins
5        1956-2 Togiak
5        1957-0 Toksook
5        1958-8 Tulukskak
5        1959-6 Tuntutuliak
5        1960-4 Tununak
5        1961-2 Twin Hills
5        1962-0 Georgetown
5        1963-8 St. Mary's
5        1964-6 Umkumiate
3    1966-1 Aleut
4      1968-7 Alutiiq Aleut
5        1969-5 Tatitlek
5        1970-3 Ugashik
4      1972-9 Bristol Bay Aleut
5        1973-7 Chignik
5        1974-5 Chignik Lake
5        1975-2 Egegik
5        1976-0 Igiugig
5        1977-8 Ivanof Bay
5        1978-6 King Salmon
5        1979-4 Kokhanok
5        1980-2 Perryville
5        1981-0 Pilot Point
5        1982-8 Port Heiden
4      1984-4 Chugach Aleut
5        1985-1 Chenega
5        1986-9 Chugach Corporation
5        1987-7 English Bay
5        1988-5 Port Graham
4      1990-1 Eyak
4      1992-7 Koniag Aleut
5        1993-5 Akhiok
5        1994-3 Agdaagux
5        1995-0 Karluk
5        1996-8 Kodiak
5        1997-6 Larsen Bay
5        1998-4 Old Harbor
5        1999-2 Ouzinkie
5        2000-8 Port Lions
4      2002-4 Sugpiaq
4      2004-0 Suqpigaq
4      2006-5 Unangan Aleut
5        2007-3 Akutan
5        2008-1 Aleut Corporation
5        2009-9 Aleutian
5        2010-7 Aleutian Islander
5        2011-5 Atka
5        2012-3 Belkofski
5        2013-1 Chignik Lagoon
5        2014-9 King Cove
5        2015-6 False Pass
5        2016-4 Nelson Lagoon
5        2017-2 Nikolski
5        2018-0 Pauloff Harbor
5        2019-8 Qagan Toyagungin
5        2020-6 Qawalangin
5        2021-4 St. George
5        2022-2 St. Paul
5        2023-0 Sand Point
5        2024-8 South Naknek
5        2025-5 Unalaska
5        2026-3 Unga
12028-9 Asian
2  2029-7 Asian Indian
2  2030-5 Bangladeshi
2  2031-3 Bhutanese
2  2032-1 Burmese
2  2033-9 Cambodian
2  2034-7 Chinese
2  2035-4 Taiwanese
2  2036-2 Filipino
2  2037-0 Hmong
2  2038-8 Indonesian
2  2039-6 Japanese
2  2040-4 Korean
2  2041-2 Laotian
2  2042-0 Malaysian
2  2043-8 Okinawan
2  2044-6 Pakistani
2  2045-3 Sri Lankan
2  2046-1 Thai
2  2047-9 Vietnamese
2  2048-7 Iwo Jiman
2  2049-5 Maldivian
2  2050-3 Nepalese
2  2051-1 Singaporean
2  2052-9 Madagascar
12054-5 Black or African American
2  2056-0 Black
2  2058-6 African American
2  2060-2 African
3    2061-0 Botswanan
3    2062-8 Ethiopian
3    2063-6 Liberian
3    2064-4 Namibian
3    2065-1 Nigerian
3    2066-9 Zairean
2  2067-7 Bahamian
2  2068-5 Barbadian
2  2069-3 Dominican
2  2070-1 Dominica Islander
2  2071-9 Haitian
2  2072-7 Jamaican
2  2073-5 Tobagoan
2  2074-3 Trinidadian
2  2075-0 West Indian
12076-8 Native Hawaiian or Other Pacific Islander
2  2078-4 Polynesian
3    2079-2 Native Hawaiian
3    2080-0 Samoan
3    2081-8 Tahitian
3    2082-6 Tongan
3    2083-4 Tokelauan
2  2085-9 Micronesian
3    2086-7 Guamanian or Chamorro
3    2087-5 Guamanian
3    2088-3 Chamorro
3    2089-1 Mariana Islander
3    2090-9 Marshallese
3    2091-7 Palauan
3    2092-5 Carolinian
3    2093-3 Kosraean
3    2094-1 Pohnpeian
3    2095-8 Saipanese
3    2096-6 Kiribati
3    2097-4 Chuukese
3    2098-2 Yapese
2  2100-6 Melanesian
3    2101-4 Fijian
3    2102-2 Papua New Guinean
3    2103-0 Solomon Islander
3    2104-8 New Hebrides
2  2500-7 Other Pacific Islander\n Note that this term remains in the table for completeness, even though within HL7, the notion of Other code is deprecated.
\r\n\n
12106-3 White
2  2108-9 European
3    2109-7 Armenian
3    2110-5 English
3    2111-3 French
3    2112-1 German
3    2113-9 Irish
3    2114-7 Italian
3    2115-4 Polish
3    2116-2 Scottish
2  2118-8 Middle Eastern or North African
3    2119-6 Assyrian
3    2120-4 Egyptian
3    2121-2 Iranian
3    2122-0 Iraqi
3    2123-8 Lebanese
3    2124-6 Palestinian
3    2125-3 Syrian
3    2126-1 Afghanistani
3    2127-9 Israeili
2  2129-5 Arab
12131-1 Other Race\n Note that this term remains in the table for completeness, even though within HL7, the notion of Other code is deprecated.
\r\n\n
\r\n
" + }, + "extension": [ + { + "url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-ballot-status", + "valueString": "External" + }, + { + "url": "http://hl7.org/fhir/StructureDefinition/structuredefinition-fmm", + "valueInteger": 0 + } + ], + "url": "http://hl7.org/fhir/v3/Race", + "identifier": { + "system": "urn:ietf:rfc:3986", + "value": "urn:oid:2.16.840.1.113883.5.104" + }, + "version": "2016-11-11", + "name": "v3 Code System Race", + "status": "active", + "experimental": false, + "date": "2016-11-11T00:00:00+11:00", + "publisher": "HL7, Inc", + "contact": [ + { + "telecom": [ + { + "system": "url", + "value": "http://hl7.org" + } + ] + } + ], + "description": " In the United States, federal standards for classifying data on race determine the categories used by federal agencies and exert a strong influence on categorization by state and local agencies and private sector organizations. The federal standards do not conceptually define race, and they recognize the absence of an anthropological or scientific basis for racial classification. Instead, the federal standards acknowledge that race is a social-political construct in which an individual's own identification with one more race categories is preferred to observer identification. The standards use a variety of features to define five minimum race categories. Among these features are descent from \"the original peoples\" of a specified region or nation. The minimum race categories are American Indian or Alaska Native, Asian, Black or African American, Native Hawaiian or Other Pacific Islander, and White. The federal standards stipulate that race data need not be limited to the five minimum categories, but any expansion must be collapsible to those categories.", + "caseSensitive": true, + "valueSet": "http://hl7.org/fhir/ValueSet/v3-Race", + "hierarchyMeaning": "is-a", + "content": "complete", + "concept": [ + { + "code": "1002-5", + "display": "American Indian or Alaska Native", + "definition": "American Indian or Alaska Native", + "concept": [ + { + "code": "1004-1", + "display": "American Indian", + "definition": "American Indian", + "concept": [ + { + "code": "1006-6", + "display": "Abenaki", + "definition": "Abenaki" + }, + { + "code": "1008-2", + "display": "Algonquian", + "definition": "Algonquian" + }, + { + "code": "1010-8", + "display": "Apache", + "definition": "Apache", + "concept": [ + { + "code": "1011-6", + "display": "Chiricahua", + "definition": "Chiricahua" + }, + { + "code": "1012-4", + "display": "Fort Sill Apache", + "definition": "Fort Sill Apache" + }, + { + "code": "1013-2", + "display": "Jicarilla Apache", + "definition": "Jicarilla Apache" + }, + { + "code": "1014-0", + "display": "Lipan Apache", + "definition": "Lipan Apache" + }, + { + "code": "1015-7", + "display": "Mescalero Apache", + "definition": "Mescalero Apache" + }, + { + "code": "1016-5", + "display": "Oklahoma Apache", + "definition": "Oklahoma Apache" + }, + { + "code": "1017-3", + "display": "Payson Apache", + "definition": "Payson Apache" + }, + { + "code": "1018-1", + "display": "San Carlos Apache", + "definition": "San Carlos Apache" + }, + { + "code": "1019-9", + "display": "White Mountain Apache", + "definition": "White Mountain Apache" + } + ] + }, + { + "code": "1021-5", + "display": "Arapaho", + "definition": "Arapaho", + "concept": [ + { + "code": "1022-3", + "display": "Northern Arapaho", + "definition": "Northern Arapaho" + }, + { + "code": "1023-1", + "display": "Southern Arapaho", + "definition": "Southern Arapaho" + }, + { + "code": "1024-9", + "display": "Wind River Arapaho", + "definition": "Wind River Arapaho" + } + ] + }, + { + "code": "1026-4", + "display": "Arikara", + "definition": "Arikara" + }, + { + "code": "1028-0", + "display": "Assiniboine", + "definition": "Assiniboine" + }, + { + "code": "1030-6", + "display": "Assiniboine Sioux", + "definition": "Assiniboine Sioux", + "concept": [ + { + "code": "1031-4", + "display": "Fort Peck Assiniboine Sioux", + "definition": "Fort Peck Assiniboine Sioux" + } + ] + }, + { + "code": "1033-0", + "display": "Bannock", + "definition": "Bannock" + }, + { + "code": "1035-5", + "display": "Blackfeet", + "definition": "Blackfeet" + }, + { + "code": "1037-1", + "display": "Brotherton", + "definition": "Brotherton" + }, + { + "code": "1039-7", + "display": "Burt Lake Band", + "definition": "Burt Lake Band" + }, + { + "code": "1041-3", + "display": "Caddo", + "definition": "Caddo", + "concept": [ + { + "code": "1042-1", + "display": "Oklahoma Cado", + "definition": "Oklahoma Cado" + } + ] + }, + { + "code": "1044-7", + "display": "Cahuilla", + "definition": "Cahuilla", + "concept": [ + { + "code": "1045-4", + "display": "Agua Caliente Cahuilla", + "definition": "Agua Caliente Cahuilla" + }, + { + "code": "1046-2", + "display": "Augustine", + "definition": "Augustine" + }, + { + "code": "1047-0", + "display": "Cabazon", + "definition": "Cabazon" + }, + { + "code": "1048-8", + "display": "Los Coyotes", + "definition": "Los Coyotes" + }, + { + "code": "1049-6", + "display": "Morongo", + "definition": "Morongo" + }, + { + "code": "1050-4", + "display": "Santa Rosa Cahuilla", + "definition": "Santa Rosa Cahuilla" + }, + { + "code": "1051-2", + "display": "Torres-Martinez", + "definition": "Torres-Martinez" + } + ] + }, + { + "code": "1053-8", + "display": "California Tribes", + "definition": "California Tribes", + "concept": [ + { + "code": "1054-6", + "display": "Cahto", + "definition": "Cahto" + }, + { + "code": "1055-3", + "display": "Chimariko", + "definition": "Chimariko" + }, + { + "code": "1056-1", + "display": "Coast Miwok", + "definition": "Coast Miwok" + }, + { + "code": "1057-9", + "display": "Digger", + "definition": "Digger" + }, + { + "code": "1058-7", + "display": "Kawaiisu", + "definition": "Kawaiisu" + }, + { + "code": "1059-5", + "display": "Kern River", + "definition": "Kern River" + }, + { + "code": "1060-3", + "display": "Mattole", + "definition": "Mattole" + }, + { + "code": "1061-1", + "display": "Red Wood", + "definition": "Red Wood" + }, + { + "code": "1062-9", + "display": "Santa Rosa", + "definition": "Santa Rosa" + }, + { + "code": "1063-7", + "display": "Takelma", + "definition": "Takelma" + }, + { + "code": "1064-5", + "display": "Wappo", + "definition": "Wappo" + }, + { + "code": "1065-2", + "display": "Yana", + "definition": "Yana" + }, + { + "code": "1066-0", + "display": "Yuki", + "definition": "Yuki" + } + ] + }, + { + "code": "1068-6", + "display": "Canadian and Latin American Indian", + "definition": "Canadian and Latin American Indian", + "concept": [ + { + "code": "1069-4", + "display": "Canadian Indian", + "definition": "Canadian Indian" + }, + { + "code": "1070-2", + "display": "Central American Indian", + "definition": "Central American Indian" + }, + { + "code": "1071-0", + "display": "French American Indian", + "definition": "French American Indian" + }, + { + "code": "1072-8", + "display": "Mexican American Indian", + "definition": "Mexican American Indian" + }, + { + "code": "1073-6", + "display": "South American Indian", + "definition": "South American Indian" + } + ] + }, + { + "code": "1074-4", + "display": "Spanish American Indian", + "definition": "Spanish American Indian" + }, + { + "code": "1076-9", + "display": "Catawba", + "definition": "Catawba", + "concept": [ + { + "code": "1741-8", + "display": "Alatna", + "definition": "Alatna" + }, + { + "code": "1742-6", + "display": "Alexander", + "definition": "Alexander" + }, + { + "code": "1743-4", + "display": "Allakaket", + "definition": "Allakaket" + }, + { + "code": "1744-2", + "display": "Alanvik", + "definition": "Alanvik" + }, + { + "code": "1745-9", + "display": "Anvik", + "definition": "Anvik" + }, + { + "code": "1746-7", + "display": "Arctic", + "definition": "Arctic" + }, + { + "code": "1747-5", + "display": "Beaver", + "definition": "Beaver" + }, + { + "code": "1748-3", + "display": "Birch Creek", + "definition": "Birch Creek" + }, + { + "code": "1749-1", + "display": "Cantwell", + "definition": "Cantwell" + }, + { + "code": "1750-9", + "display": "Chalkyitsik", + "definition": "Chalkyitsik" + }, + { + "code": "1751-7", + "display": "Chickaloon", + "definition": "Chickaloon" + }, + { + "code": "1752-5", + "display": "Chistochina", + "definition": "Chistochina" + }, + { + "code": "1753-3", + "display": "Chitina", + "definition": "Chitina" + }, + { + "code": "1754-1", + "display": "Circle", + "definition": "Circle" + }, + { + "code": "1755-8", + "display": "Cook Inlet", + "definition": "Cook Inlet" + }, + { + "code": "1756-6", + "display": "Copper Center", + "definition": "Copper Center" + }, + { + "code": "1757-4", + "display": "Copper River", + "definition": "Copper River" + }, + { + "code": "1758-2", + "display": "Dot Lake", + "definition": "Dot Lake" + }, + { + "code": "1759-0", + "display": "Doyon", + "definition": "Doyon" + }, + { + "code": "1760-8", + "display": "Eagle", + "definition": "Eagle" + }, + { + "code": "1761-6", + "display": "Eklutna", + "definition": "Eklutna" + }, + { + "code": "1762-4", + "display": "Evansville", + "definition": "Evansville" + }, + { + "code": "1763-2", + "display": "Fort Yukon", + "definition": "Fort Yukon" + }, + { + "code": "1764-0", + "display": "Gakona", + "definition": "Gakona" + }, + { + "code": "1765-7", + "display": "Galena", + "definition": "Galena" + }, + { + "code": "1766-5", + "display": "Grayling", + "definition": "Grayling" + }, + { + "code": "1767-3", + "display": "Gulkana", + "definition": "Gulkana" + }, + { + "code": "1768-1", + "display": "Healy Lake", + "definition": "Healy Lake" + }, + { + "code": "1769-9", + "display": "Holy Cross", + "definition": "Holy Cross" + }, + { + "code": "1770-7", + "display": "Hughes", + "definition": "Hughes" + }, + { + "code": "1771-5", + "display": "Huslia", + "definition": "Huslia" + }, + { + "code": "1772-3", + "display": "Iliamna", + "definition": "Iliamna" + }, + { + "code": "1773-1", + "display": "Kaltag", + "definition": "Kaltag" + }, + { + "code": "1774-9", + "display": "Kluti Kaah", + "definition": "Kluti Kaah" + }, + { + "code": "1775-6", + "display": "Knik", + "definition": "Knik" + }, + { + "code": "1776-4", + "display": "Koyukuk", + "definition": "Koyukuk" + }, + { + "code": "1777-2", + "display": "Lake Minchumina", + "definition": "Lake Minchumina" + }, + { + "code": "1778-0", + "display": "Lime", + "definition": "Lime" + }, + { + "code": "1779-8", + "display": "Mcgrath", + "definition": "Mcgrath" + }, + { + "code": "1780-6", + "display": "Manley Hot Springs", + "definition": "Manley Hot Springs" + }, + { + "code": "1781-4", + "display": "Mentasta Lake", + "definition": "Mentasta Lake" + }, + { + "code": "1782-2", + "display": "Minto", + "definition": "Minto" + }, + { + "code": "1783-0", + "display": "Nenana", + "definition": "Nenana" + }, + { + "code": "1784-8", + "display": "Nikolai", + "definition": "Nikolai" + }, + { + "code": "1785-5", + "display": "Ninilchik", + "definition": "Ninilchik" + }, + { + "code": "1786-3", + "display": "Nondalton", + "definition": "Nondalton" + }, + { + "code": "1787-1", + "display": "Northway", + "definition": "Northway" + }, + { + "code": "1788-9", + "display": "Nulato", + "definition": "Nulato" + }, + { + "code": "1789-7", + "display": "Pedro Bay", + "definition": "Pedro Bay" + }, + { + "code": "1790-5", + "display": "Rampart", + "definition": "Rampart" + }, + { + "code": "1791-3", + "display": "Ruby", + "definition": "Ruby" + }, + { + "code": "1792-1", + "display": "Salamatof", + "definition": "Salamatof" + }, + { + "code": "1793-9", + "display": "Seldovia", + "definition": "Seldovia" + }, + { + "code": "1794-7", + "display": "Slana", + "definition": "Slana" + }, + { + "code": "1795-4", + "display": "Shageluk", + "definition": "Shageluk" + }, + { + "code": "1796-2", + "display": "Stevens", + "definition": "Stevens" + }, + { + "code": "1797-0", + "display": "Stony River", + "definition": "Stony River" + }, + { + "code": "1798-8", + "display": "Takotna", + "definition": "Takotna" + }, + { + "code": "1799-6", + "display": "Tanacross", + "definition": "Tanacross" + }, + { + "code": "1800-2", + "display": "Tanaina", + "definition": "Tanaina" + }, + { + "code": "1801-0", + "display": "Tanana", + "definition": "Tanana" + }, + { + "code": "1802-8", + "display": "Tanana Chiefs", + "definition": "Tanana Chiefs" + }, + { + "code": "1803-6", + "display": "Tazlina", + "definition": "Tazlina" + }, + { + "code": "1804-4", + "display": "Telida", + "definition": "Telida" + }, + { + "code": "1805-1", + "display": "Tetlin", + "definition": "Tetlin" + }, + { + "code": "1806-9", + "display": "Tok", + "definition": "Tok" + }, + { + "code": "1807-7", + "display": "Tyonek", + "definition": "Tyonek" + }, + { + "code": "1808-5", + "display": "Venetie", + "definition": "Venetie" + }, + { + "code": "1809-3", + "display": "Wiseman", + "definition": "Wiseman" + } + ] + }, + { + "code": "1078-5", + "display": "Cayuse", + "definition": "Cayuse" + }, + { + "code": "1080-1", + "display": "Chehalis", + "definition": "Chehalis" + }, + { + "code": "1082-7", + "display": "Chemakuan", + "definition": "Chemakuan", + "concept": [ + { + "code": "1083-5", + "display": "Hoh", + "definition": "Hoh" + }, + { + "code": "1084-3", + "display": "Quileute", + "definition": "Quileute" + } + ] + }, + { + "code": "1086-8", + "display": "Chemehuevi", + "definition": "Chemehuevi" + }, + { + "code": "1088-4", + "display": "Cherokee", + "definition": "Cherokee", + "concept": [ + { + "code": "1089-2", + "display": "Cherokee Alabama", + "definition": "Cherokee Alabama" + }, + { + "code": "1090-0", + "display": "Cherokees of Northeast Alabama", + "definition": "Cherokees of Northeast Alabama" + }, + { + "code": "1091-8", + "display": "Cherokees of Southeast Alabama", + "definition": "Cherokees of Southeast Alabama" + }, + { + "code": "1092-6", + "display": "Eastern Cherokee", + "definition": "Eastern Cherokee" + }, + { + "code": "1093-4", + "display": "Echota Cherokee", + "definition": "Echota Cherokee" + }, + { + "code": "1094-2", + "display": "Etowah Cherokee", + "definition": "Etowah Cherokee" + }, + { + "code": "1095-9", + "display": "Northern Cherokee", + "definition": "Northern Cherokee" + }, + { + "code": "1096-7", + "display": "Tuscola", + "definition": "Tuscola" + }, + { + "code": "1097-5", + "display": "United Keetowah Band of Cherokee", + "definition": "United Keetowah Band of Cherokee" + }, + { + "code": "1098-3", + "display": "Western Cherokee", + "definition": "Western Cherokee" + } + ] + }, + { + "code": "1100-7", + "display": "Cherokee Shawnee", + "definition": "Cherokee Shawnee" + }, + { + "code": "1102-3", + "display": "Cheyenne", + "definition": "Cheyenne", + "concept": [ + { + "code": "1103-1", + "display": "Northern Cheyenne", + "definition": "Northern Cheyenne" + }, + { + "code": "1104-9", + "display": "Southern Cheyenne", + "definition": "Southern Cheyenne" + } + ] + }, + { + "code": "1106-4", + "display": "Cheyenne-Arapaho", + "definition": "Cheyenne-Arapaho" + }, + { + "code": "1108-0", + "display": "Chickahominy", + "definition": "Chickahominy", + "concept": [ + { + "code": "1109-8", + "display": "Eastern Chickahominy", + "definition": "Eastern Chickahominy" + }, + { + "code": "1110-6", + "display": "Western Chickahominy", + "definition": "Western Chickahominy" + } + ] + }, + { + "code": "1112-2", + "display": "Chickasaw", + "definition": "Chickasaw" + }, + { + "code": "1114-8", + "display": "Chinook", + "definition": "Chinook", + "concept": [ + { + "code": "1115-5", + "display": "Clatsop", + "definition": "Clatsop" + }, + { + "code": "1116-3", + "display": "Columbia River Chinook", + "definition": "Columbia River Chinook" + }, + { + "code": "1117-1", + "display": "Kathlamet", + "definition": "Kathlamet" + }, + { + "code": "1118-9", + "display": "Upper Chinook", + "definition": "Upper Chinook" + }, + { + "code": "1119-7", + "display": "Wakiakum Chinook", + "definition": "Wakiakum Chinook" + }, + { + "code": "1120-5", + "display": "Willapa Chinook", + "definition": "Willapa Chinook" + }, + { + "code": "1121-3", + "display": "Wishram", + "definition": "Wishram" + } + ] + }, + { + "code": "1123-9", + "display": "Chippewa", + "definition": "Chippewa", + "concept": [ + { + "code": "1124-7", + "display": "Bad River", + "definition": "Bad River" + }, + { + "code": "1125-4", + "display": "Bay Mills Chippewa", + "definition": "Bay Mills Chippewa" + }, + { + "code": "1126-2", + "display": "Bois Forte", + "definition": "Bois Forte" + }, + { + "code": "1127-0", + "display": "Burt Lake Chippewa", + "definition": "Burt Lake Chippewa" + }, + { + "code": "1128-8", + "display": "Fond du Lac", + "definition": "Fond du Lac" + }, + { + "code": "1129-6", + "display": "Grand Portage", + "definition": "Grand Portage" + }, + { + "code": "1130-4", + "display": "Grand Traverse Band of Ottawa-Chippewa", + "definition": "Grand Traverse Band of Ottawa-Chippewa" + }, + { + "code": "1131-2", + "display": "Keweenaw", + "definition": "Keweenaw" + }, + { + "code": "1132-0", + "display": "Lac Courte Oreilles", + "definition": "Lac Courte Oreilles" + }, + { + "code": "1133-8", + "display": "Lac du Flambeau", + "definition": "Lac du Flambeau" + }, + { + "code": "1134-6", + "display": "Lac Vieux Desert Chippewa", + "definition": "Lac Vieux Desert Chippewa" + }, + { + "code": "1135-3", + "display": "Lake Superior", + "definition": "Lake Superior" + }, + { + "code": "1136-1", + "display": "Leech Lake", + "definition": "Leech Lake" + }, + { + "code": "1137-9", + "display": "Little Shell Chippewa", + "definition": "Little Shell Chippewa" + }, + { + "code": "1138-7", + "display": "Mille Lacs", + "definition": "Mille Lacs" + }, + { + "code": "1139-5", + "display": "Minnesota Chippewa", + "definition": "Minnesota Chippewa" + }, + { + "code": "1140-3", + "display": "Ontonagon", + "definition": "Ontonagon" + }, + { + "code": "1141-1", + "display": "Red Cliff Chippewa", + "definition": "Red Cliff Chippewa" + }, + { + "code": "1142-9", + "display": "Red Lake Chippewa", + "definition": "Red Lake Chippewa" + }, + { + "code": "1143-7", + "display": "Saginaw Chippewa", + "definition": "Saginaw Chippewa" + }, + { + "code": "1144-5", + "display": "St. Croix Chippewa", + "definition": "St. Croix Chippewa" + }, + { + "code": "1145-2", + "display": "Sault Ste. Marie Chippewa", + "definition": "Sault Ste. Marie Chippewa" + }, + { + "code": "1146-0", + "display": "Sokoagon Chippewa", + "definition": "Sokoagon Chippewa" + }, + { + "code": "1147-8", + "display": "Turtle Mountain", + "definition": "Turtle Mountain" + }, + { + "code": "1148-6", + "display": "White Earth", + "definition": "White Earth" + } + ] + }, + { + "code": "1150-2", + "display": "Chippewa Cree", + "definition": "Chippewa Cree", + "concept": [ + { + "code": "1151-0", + "display": "Rocky Boy's Chippewa Cree", + "definition": "Rocky Boy's Chippewa Cree" + } + ] + }, + { + "code": "1153-6", + "display": "Chitimacha", + "definition": "Chitimacha" + }, + { + "code": "1155-1", + "display": "Choctaw", + "definition": "Choctaw", + "concept": [ + { + "code": "1156-9", + "display": "Clifton Choctaw", + "definition": "Clifton Choctaw" + }, + { + "code": "1157-7", + "display": "Jena Choctaw", + "definition": "Jena Choctaw" + }, + { + "code": "1158-5", + "display": "Mississippi Choctaw", + "definition": "Mississippi Choctaw" + }, + { + "code": "1159-3", + "display": "Mowa Band of Choctaw", + "definition": "Mowa Band of Choctaw" + }, + { + "code": "1160-1", + "display": "Oklahoma Choctaw", + "definition": "Oklahoma Choctaw" + } + ] + }, + { + "code": "1162-7", + "display": "Chumash", + "definition": "Chumash", + "concept": [ + { + "code": "1163-5", + "display": "Santa Ynez", + "definition": "Santa Ynez" + } + ] + }, + { + "code": "1165-0", + "display": "Clear Lake", + "definition": "Clear Lake" + }, + { + "code": "1167-6", + "display": "Coeur D'Alene", + "definition": "Coeur D'Alene" + }, + { + "code": "1169-2", + "display": "Coharie", + "definition": "Coharie" + }, + { + "code": "1171-8", + "display": "Colorado River", + "definition": "Colorado River" + }, + { + "code": "1173-4", + "display": "Colville", + "definition": "Colville" + }, + { + "code": "1175-9", + "display": "Comanche", + "definition": "Comanche", + "concept": [ + { + "code": "1176-7", + "display": "Oklahoma Comanche", + "definition": "Oklahoma Comanche" + } + ] + }, + { + "code": "1178-3", + "display": "Coos, Lower Umpqua, Siuslaw", + "definition": "Coos, Lower Umpqua, Siuslaw" + }, + { + "code": "1180-9", + "display": "Coos", + "definition": "Coos" + }, + { + "code": "1182-5", + "display": "Coquilles", + "definition": "Coquilles" + }, + { + "code": "1184-1", + "display": "Costanoan", + "definition": "Costanoan" + }, + { + "code": "1186-6", + "display": "Coushatta", + "definition": "Coushatta", + "concept": [ + { + "code": "1187-4", + "display": "Alabama Coushatta", + "definition": "Alabama Coushatta" + } + ] + }, + { + "code": "1189-0", + "display": "Cowlitz", + "definition": "Cowlitz" + }, + { + "code": "1191-6", + "display": "Cree", + "definition": "Cree" + }, + { + "code": "1193-2", + "display": "Creek", + "definition": "Creek", + "concept": [ + { + "code": "1194-0", + "display": "Alabama Creek", + "definition": "Alabama Creek" + }, + { + "code": "1195-7", + "display": "Alabama Quassarte", + "definition": "Alabama Quassarte" + }, + { + "code": "1196-5", + "display": "Eastern Creek", + "definition": "Eastern Creek" + }, + { + "code": "1197-3", + "display": "Eastern Muscogee", + "definition": "Eastern Muscogee" + }, + { + "code": "1198-1", + "display": "Kialegee", + "definition": "Kialegee" + }, + { + "code": "1199-9", + "display": "Lower Muscogee", + "definition": "Lower Muscogee" + }, + { + "code": "1200-5", + "display": "Machis Lower Creek Indian", + "definition": "Machis Lower Creek Indian" + }, + { + "code": "1201-3", + "display": "Poarch Band", + "definition": "Poarch Band" + }, + { + "code": "1202-1", + "display": "Principal Creek Indian Nation", + "definition": "Principal Creek Indian Nation" + }, + { + "code": "1203-9", + "display": "Star Clan of Muscogee Creeks", + "definition": "Star Clan of Muscogee Creeks" + }, + { + "code": "1204-7", + "display": "Thlopthlocco", + "definition": "Thlopthlocco" + }, + { + "code": "1205-4", + "display": "Tuckabachee", + "definition": "Tuckabachee" + } + ] + }, + { + "code": "1207-0", + "display": "Croatan", + "definition": "Croatan" + }, + { + "code": "1209-6", + "display": "Crow", + "definition": "Crow" + }, + { + "code": "1211-2", + "display": "Cupeno", + "definition": "Cupeno", + "concept": [ + { + "code": "1212-0", + "display": "Agua Caliente", + "definition": "Agua Caliente" + } + ] + }, + { + "code": "1214-6", + "display": "Delaware", + "definition": "Delaware", + "concept": [ + { + "code": "1215-3", + "display": "Eastern Delaware", + "definition": "Eastern Delaware" + }, + { + "code": "1216-1", + "display": "Lenni-Lenape", + "definition": "Lenni-Lenape" + }, + { + "code": "1217-9", + "display": "Munsee", + "definition": "Munsee" + }, + { + "code": "1218-7", + "display": "Oklahoma Delaware", + "definition": "Oklahoma Delaware" + }, + { + "code": "1219-5", + "display": "Rampough Mountain", + "definition": "Rampough Mountain" + }, + { + "code": "1220-3", + "display": "Sand Hill", + "definition": "Sand Hill" + } + ] + }, + { + "code": "1222-9", + "display": "Diegueno", + "definition": "Diegueno", + "concept": [ + { + "code": "1223-7", + "display": "Campo", + "definition": "Campo" + }, + { + "code": "1224-5", + "display": "Capitan Grande", + "definition": "Capitan Grande" + }, + { + "code": "1225-2", + "display": "Cuyapaipe", + "definition": "Cuyapaipe" + }, + { + "code": "1226-0", + "display": "La Posta", + "definition": "La Posta" + }, + { + "code": "1227-8", + "display": "Manzanita", + "definition": "Manzanita" + }, + { + "code": "1228-6", + "display": "Mesa Grande", + "definition": "Mesa Grande" + }, + { + "code": "1229-4", + "display": "San Pasqual", + "definition": "San Pasqual" + }, + { + "code": "1230-2", + "display": "Santa Ysabel", + "definition": "Santa Ysabel" + }, + { + "code": "1231-0", + "display": "Sycuan", + "definition": "Sycuan" + } + ] + }, + { + "code": "1233-6", + "display": "Eastern Tribes", + "definition": "Eastern Tribes", + "concept": [ + { + "code": "1234-4", + "display": "Attacapa", + "definition": "Attacapa" + }, + { + "code": "1235-1", + "display": "Biloxi", + "definition": "Biloxi" + }, + { + "code": "1236-9", + "display": "Georgetown", + "definition": "Georgetown" + }, + { + "code": "1237-7", + "display": "Moor", + "definition": "Moor" + }, + { + "code": "1238-5", + "display": "Nansemond", + "definition": "Nansemond" + }, + { + "code": "1239-3", + "display": "Natchez", + "definition": "Natchez" + }, + { + "code": "1240-1", + "display": "Nausu Waiwash", + "definition": "Nausu Waiwash" + }, + { + "code": "1241-9", + "display": "Nipmuc", + "definition": "Nipmuc" + }, + { + "code": "1242-7", + "display": "Paugussett", + "definition": "Paugussett" + }, + { + "code": "1243-5", + "display": "Pocomoke Acohonock", + "definition": "Pocomoke Acohonock" + }, + { + "code": "1244-3", + "display": "Southeastern Indians", + "definition": "Southeastern Indians" + }, + { + "code": "1245-0", + "display": "Susquehanock", + "definition": "Susquehanock" + }, + { + "code": "1246-8", + "display": "Tunica Biloxi", + "definition": "Tunica Biloxi" + }, + { + "code": "1247-6", + "display": "Waccamaw-Siousan", + "definition": "Waccamaw-Siousan" + }, + { + "code": "1248-4", + "display": "Wicomico", + "definition": "Wicomico" + } + ] + }, + { + "code": "1250-0", + "display": "Esselen", + "definition": "Esselen" + }, + { + "code": "1252-6", + "display": "Fort Belknap", + "definition": "Fort Belknap" + }, + { + "code": "1254-2", + "display": "Fort Berthold", + "definition": "Fort Berthold" + }, + { + "code": "1256-7", + "display": "Fort Mcdowell", + "definition": "Fort Mcdowell" + }, + { + "code": "1258-3", + "display": "Fort Hall", + "definition": "Fort Hall" + }, + { + "code": "1260-9", + "display": "Gabrieleno", + "definition": "Gabrieleno" + }, + { + "code": "1262-5", + "display": "Grand Ronde", + "definition": "Grand Ronde" + }, + { + "code": "1264-1", + "display": "Gros Ventres", + "definition": "Gros Ventres", + "concept": [ + { + "code": "1265-8", + "display": "Atsina", + "definition": "Atsina" + } + ] + }, + { + "code": "1267-4", + "display": "Haliwa", + "definition": "Haliwa" + }, + { + "code": "1269-0", + "display": "Hidatsa", + "definition": "Hidatsa" + }, + { + "code": "1271-6", + "display": "Hoopa", + "definition": "Hoopa", + "concept": [ + { + "code": "1272-4", + "display": "Trinity", + "definition": "Trinity" + }, + { + "code": "1273-2", + "display": "Whilkut", + "definition": "Whilkut" + } + ] + }, + { + "code": "1275-7", + "display": "Hoopa Extension", + "definition": "Hoopa Extension" + }, + { + "code": "1277-3", + "display": "Houma", + "definition": "Houma" + }, + { + "code": "1279-9", + "display": "Inaja-Cosmit", + "definition": "Inaja-Cosmit" + }, + { + "code": "1281-5", + "display": "Iowa", + "definition": "Iowa", + "concept": [ + { + "code": "1282-3", + "display": "Iowa of Kansas-Nebraska", + "definition": "Iowa of Kansas-Nebraska" + }, + { + "code": "1283-1", + "display": "Iowa of Oklahoma", + "definition": "Iowa of Oklahoma" + } + ] + }, + { + "code": "1285-6", + "display": "Iroquois", + "definition": "Iroquois", + "concept": [ + { + "code": "1286-4", + "display": "Cayuga", + "definition": "Cayuga" + }, + { + "code": "1287-2", + "display": "Mohawk", + "definition": "Mohawk" + }, + { + "code": "1288-0", + "display": "Oneida", + "definition": "Oneida" + }, + { + "code": "1289-8", + "display": "Onondaga", + "definition": "Onondaga" + }, + { + "code": "1290-6", + "display": "Seneca", + "definition": "Seneca" + }, + { + "code": "1291-4", + "display": "Seneca Nation", + "definition": "Seneca Nation" + }, + { + "code": "1292-2", + "display": "Seneca-Cayuga", + "definition": "Seneca-Cayuga" + }, + { + "code": "1293-0", + "display": "Tonawanda Seneca", + "definition": "Tonawanda Seneca" + }, + { + "code": "1294-8", + "display": "Tuscarora", + "definition": "Tuscarora" + }, + { + "code": "1295-5", + "display": "Wyandotte", + "definition": "Wyandotte" + } + ] + }, + { + "code": "1297-1", + "display": "Juaneno", + "definition": "Juaneno" + }, + { + "code": "1299-7", + "display": "Kalispel", + "definition": "Kalispel" + }, + { + "code": "1301-1", + "display": "Karuk", + "definition": "Karuk" + }, + { + "code": "1303-7", + "display": "Kaw", + "definition": "Kaw" + }, + { + "code": "1305-2", + "display": "Kickapoo", + "definition": "Kickapoo", + "concept": [ + { + "code": "1306-0", + "display": "Oklahoma Kickapoo", + "definition": "Oklahoma Kickapoo" + }, + { + "code": "1307-8", + "display": "Texas Kickapoo", + "definition": "Texas Kickapoo" + } + ] + }, + { + "code": "1309-4", + "display": "Kiowa", + "definition": "Kiowa", + "concept": [ + { + "code": "1310-2", + "display": "Oklahoma Kiowa", + "definition": "Oklahoma Kiowa" + } + ] + }, + { + "code": "1312-8", + "display": "Klallam", + "definition": "Klallam", + "concept": [ + { + "code": "1313-6", + "display": "Jamestown", + "definition": "Jamestown" + }, + { + "code": "1314-4", + "display": "Lower Elwha", + "definition": "Lower Elwha" + }, + { + "code": "1315-1", + "display": "Port Gamble Klallam", + "definition": "Port Gamble Klallam" + } + ] + }, + { + "code": "1317-7", + "display": "Klamath", + "definition": "Klamath" + }, + { + "code": "1319-3", + "display": "Konkow", + "definition": "Konkow" + }, + { + "code": "1321-9", + "display": "Kootenai", + "definition": "Kootenai" + }, + { + "code": "1323-5", + "display": "Lassik", + "definition": "Lassik" + }, + { + "code": "1325-0", + "display": "Long Island", + "definition": "Long Island", + "concept": [ + { + "code": "1326-8", + "display": "Matinecock", + "definition": "Matinecock" + }, + { + "code": "1327-6", + "display": "Montauk", + "definition": "Montauk" + }, + { + "code": "1328-4", + "display": "Poospatuck", + "definition": "Poospatuck" + }, + { + "code": "1329-2", + "display": "Setauket", + "definition": "Setauket" + } + ] + }, + { + "code": "1331-8", + "display": "Luiseno", + "definition": "Luiseno", + "concept": [ + { + "code": "1332-6", + "display": "La Jolla", + "definition": "La Jolla" + }, + { + "code": "1333-4", + "display": "Pala", + "definition": "Pala" + }, + { + "code": "1334-2", + "display": "Pauma", + "definition": "Pauma" + }, + { + "code": "1335-9", + "display": "Pechanga", + "definition": "Pechanga" + }, + { + "code": "1336-7", + "display": "Soboba", + "definition": "Soboba" + }, + { + "code": "1337-5", + "display": "Twenty-Nine Palms", + "definition": "Twenty-Nine Palms" + }, + { + "code": "1338-3", + "display": "Temecula", + "definition": "Temecula" + } + ] + }, + { + "code": "1340-9", + "display": "Lumbee", + "definition": "Lumbee" + }, + { + "code": "1342-5", + "display": "Lummi", + "definition": "Lummi" + }, + { + "code": "1344-1", + "display": "Maidu", + "definition": "Maidu", + "concept": [ + { + "code": "1345-8", + "display": "Mountain Maidu", + "definition": "Mountain Maidu" + }, + { + "code": "1346-6", + "display": "Nishinam", + "definition": "Nishinam" + } + ] + }, + { + "code": "1348-2", + "display": "Makah", + "definition": "Makah" + }, + { + "code": "1350-8", + "display": "Maliseet", + "definition": "Maliseet" + }, + { + "code": "1352-4", + "display": "Mandan", + "definition": "Mandan" + }, + { + "code": "1354-0", + "display": "Mattaponi", + "definition": "Mattaponi" + }, + { + "code": "1356-5", + "display": "Menominee", + "definition": "Menominee" + }, + { + "code": "1358-1", + "display": "Miami", + "definition": "Miami", + "concept": [ + { + "code": "1359-9", + "display": "Illinois Miami", + "definition": "Illinois Miami" + }, + { + "code": "1360-7", + "display": "Indiana Miami", + "definition": "Indiana Miami" + }, + { + "code": "1361-5", + "display": "Oklahoma Miami", + "definition": "Oklahoma Miami" + } + ] + }, + { + "code": "1363-1", + "display": "Miccosukee", + "definition": "Miccosukee" + }, + { + "code": "1365-6", + "display": "Micmac", + "definition": "Micmac", + "concept": [ + { + "code": "1366-4", + "display": "Aroostook", + "definition": "Aroostook" + } + ] + }, + { + "code": "1368-0", + "display": "Mission Indians", + "definition": "Mission Indians" + }, + { + "code": "1370-6", + "display": "Miwok", + "definition": "Miwok" + }, + { + "code": "1372-2", + "display": "Modoc", + "definition": "Modoc" + }, + { + "code": "1374-8", + "display": "Mohegan", + "definition": "Mohegan" + }, + { + "code": "1376-3", + "display": "Mono", + "definition": "Mono" + }, + { + "code": "1378-9", + "display": "Nanticoke", + "definition": "Nanticoke" + }, + { + "code": "1380-5", + "display": "Narragansett", + "definition": "Narragansett" + }, + { + "code": "1382-1", + "display": "Navajo", + "definition": "Navajo", + "concept": [ + { + "code": "1383-9", + "display": "Alamo Navajo", + "definition": "Alamo Navajo" + }, + { + "code": "1384-7", + "display": "Canoncito Navajo", + "definition": "Canoncito Navajo" + }, + { + "code": "1385-4", + "display": "Ramah Navajo", + "definition": "Ramah Navajo" + } + ] + }, + { + "code": "1387-0", + "display": "Nez Perce", + "definition": "Nez Perce" + }, + { + "code": "1389-6", + "display": "Nomalaki", + "definition": "Nomalaki" + }, + { + "code": "1391-2", + "display": "Northwest Tribes", + "definition": "Northwest Tribes", + "concept": [ + { + "code": "1392-0", + "display": "Alsea", + "definition": "Alsea" + }, + { + "code": "1393-8", + "display": "Celilo", + "definition": "Celilo" + }, + { + "code": "1394-6", + "display": "Columbia", + "definition": "Columbia" + }, + { + "code": "1395-3", + "display": "Kalapuya", + "definition": "Kalapuya" + }, + { + "code": "1396-1", + "display": "Molala", + "definition": "Molala" + }, + { + "code": "1397-9", + "display": "Talakamish", + "definition": "Talakamish" + }, + { + "code": "1398-7", + "display": "Tenino", + "definition": "Tenino" + }, + { + "code": "1399-5", + "display": "Tillamook", + "definition": "Tillamook" + }, + { + "code": "1400-1", + "display": "Wenatchee", + "definition": "Wenatchee" + }, + { + "code": "1401-9", + "display": "Yahooskin", + "definition": "Yahooskin" + } + ] + }, + { + "code": "1403-5", + "display": "Omaha", + "definition": "Omaha" + }, + { + "code": "1405-0", + "display": "Oregon Athabaskan", + "definition": "Oregon Athabaskan" + }, + { + "code": "1407-6", + "display": "Osage", + "definition": "Osage" + }, + { + "code": "1409-2", + "display": "Otoe-Missouria", + "definition": "Otoe-Missouria" + }, + { + "code": "1411-8", + "display": "Ottawa", + "definition": "Ottawa", + "concept": [ + { + "code": "1412-6", + "display": "Burt Lake Ottawa", + "definition": "Burt Lake Ottawa" + }, + { + "code": "1413-4", + "display": "Michigan Ottawa", + "definition": "Michigan Ottawa" + }, + { + "code": "1414-2", + "display": "Oklahoma Ottawa", + "definition": "Oklahoma Ottawa" + } + ] + }, + { + "code": "1416-7", + "display": "Paiute", + "definition": "Paiute", + "concept": [ + { + "code": "1417-5", + "display": "Bishop", + "definition": "Bishop" + }, + { + "code": "1418-3", + "display": "Bridgeport", + "definition": "Bridgeport" + }, + { + "code": "1419-1", + "display": "Burns Paiute", + "definition": "Burns Paiute" + }, + { + "code": "1420-9", + "display": "Cedarville", + "definition": "Cedarville" + }, + { + "code": "1421-7", + "display": "Fort Bidwell", + "definition": "Fort Bidwell" + }, + { + "code": "1422-5", + "display": "Fort Independence", + "definition": "Fort Independence" + }, + { + "code": "1423-3", + "display": "Kaibab", + "definition": "Kaibab" + }, + { + "code": "1424-1", + "display": "Las Vegas", + "definition": "Las Vegas" + }, + { + "code": "1425-8", + "display": "Lone Pine", + "definition": "Lone Pine" + }, + { + "code": "1426-6", + "display": "Lovelock", + "definition": "Lovelock" + }, + { + "code": "1427-4", + "display": "Malheur Paiute", + "definition": "Malheur Paiute" + }, + { + "code": "1428-2", + "display": "Moapa", + "definition": "Moapa" + }, + { + "code": "1429-0", + "display": "Northern Paiute", + "definition": "Northern Paiute" + }, + { + "code": "1430-8", + "display": "Owens Valley", + "definition": "Owens Valley" + }, + { + "code": "1431-6", + "display": "Pyramid Lake", + "definition": "Pyramid Lake" + }, + { + "code": "1432-4", + "display": "San Juan Southern Paiute", + "definition": "San Juan Southern Paiute" + }, + { + "code": "1433-2", + "display": "Southern Paiute", + "definition": "Southern Paiute" + }, + { + "code": "1434-0", + "display": "Summit Lake", + "definition": "Summit Lake" + }, + { + "code": "1435-7", + "display": "Utu Utu Gwaitu Paiute", + "definition": "Utu Utu Gwaitu Paiute" + }, + { + "code": "1436-5", + "display": "Walker River", + "definition": "Walker River" + }, + { + "code": "1437-3", + "display": "Yerington Paiute", + "definition": "Yerington Paiute" + } + ] + }, + { + "code": "1439-9", + "display": "Pamunkey", + "definition": "Pamunkey" + }, + { + "code": "1441-5", + "display": "Passamaquoddy", + "definition": "Passamaquoddy", + "concept": [ + { + "code": "1442-3", + "display": "Indian Township", + "definition": "Indian Township" + }, + { + "code": "1443-1", + "display": "Pleasant Point Passamaquoddy", + "definition": "Pleasant Point Passamaquoddy" + } + ] + }, + { + "code": "1445-6", + "display": "Pawnee", + "definition": "Pawnee", + "concept": [ + { + "code": "1446-4", + "display": "Oklahoma Pawnee", + "definition": "Oklahoma Pawnee" + } + ] + }, + { + "code": "1448-0", + "display": "Penobscot", + "definition": "Penobscot" + }, + { + "code": "1450-6", + "display": "Peoria", + "definition": "Peoria", + "concept": [ + { + "code": "1451-4", + "display": "Oklahoma Peoria", + "definition": "Oklahoma Peoria" + } + ] + }, + { + "code": "1453-0", + "display": "Pequot", + "definition": "Pequot", + "concept": [ + { + "code": "1454-8", + "display": "Marshantucket Pequot", + "definition": "Marshantucket Pequot" + } + ] + }, + { + "code": "1456-3", + "display": "Pima", + "definition": "Pima", + "concept": [ + { + "code": "1457-1", + "display": "Gila River Pima-Maricopa", + "definition": "Gila River Pima-Maricopa" + }, + { + "code": "1458-9", + "display": "Salt River Pima-Maricopa", + "definition": "Salt River Pima-Maricopa" + } + ] + }, + { + "code": "1460-5", + "display": "Piscataway", + "definition": "Piscataway" + }, + { + "code": "1462-1", + "display": "Pit River", + "definition": "Pit River" + }, + { + "code": "1464-7", + "display": "Pomo", + "definition": "Pomo", + "concept": [ + { + "code": "1465-4", + "display": "Central Pomo", + "definition": "Central Pomo" + }, + { + "code": "1466-2", + "display": "Dry Creek", + "definition": "Dry Creek" + }, + { + "code": "1467-0", + "display": "Eastern Pomo", + "definition": "Eastern Pomo" + }, + { + "code": "1468-8", + "display": "Kashia", + "definition": "Kashia" + }, + { + "code": "1469-6", + "display": "Northern Pomo", + "definition": "Northern Pomo" + }, + { + "code": "1470-4", + "display": "Scotts Valley", + "definition": "Scotts Valley" + }, + { + "code": "1471-2", + "display": "Stonyford", + "definition": "Stonyford" + }, + { + "code": "1472-0", + "display": "Sulphur Bank", + "definition": "Sulphur Bank" + } + ] + }, + { + "code": "1474-6", + "display": "Ponca", + "definition": "Ponca", + "concept": [ + { + "code": "1475-3", + "display": "Nebraska Ponca", + "definition": "Nebraska Ponca" + }, + { + "code": "1476-1", + "display": "Oklahoma Ponca", + "definition": "Oklahoma Ponca" + } + ] + }, + { + "code": "1478-7", + "display": "Potawatomi", + "definition": "Potawatomi", + "concept": [ + { + "code": "1479-5", + "display": "Citizen Band Potawatomi", + "definition": "Citizen Band Potawatomi" + }, + { + "code": "1480-3", + "display": "Forest County", + "definition": "Forest County" + }, + { + "code": "1481-1", + "display": "Hannahville", + "definition": "Hannahville" + }, + { + "code": "1482-9", + "display": "Huron Potawatomi", + "definition": "Huron Potawatomi" + }, + { + "code": "1483-7", + "display": "Pokagon Potawatomi", + "definition": "Pokagon Potawatomi" + }, + { + "code": "1484-5", + "display": "Prairie Band", + "definition": "Prairie Band" + }, + { + "code": "1485-2", + "display": "Wisconsin Potawatomi", + "definition": "Wisconsin Potawatomi" + } + ] + }, + { + "code": "1487-8", + "display": "Powhatan", + "definition": "Powhatan" + }, + { + "code": "1489-4", + "display": "Pueblo", + "definition": "Pueblo", + "concept": [ + { + "code": "1490-2", + "display": "Acoma", + "definition": "Acoma" + }, + { + "code": "1491-0", + "display": "Arizona Tewa", + "definition": "Arizona Tewa" + }, + { + "code": "1492-8", + "display": "Cochiti", + "definition": "Cochiti" + }, + { + "code": "1493-6", + "display": "Hopi", + "definition": "Hopi" + }, + { + "code": "1494-4", + "display": "Isleta", + "definition": "Isleta" + }, + { + "code": "1495-1", + "display": "Jemez", + "definition": "Jemez" + }, + { + "code": "1496-9", + "display": "Keres", + "definition": "Keres" + }, + { + "code": "1497-7", + "display": "Laguna", + "definition": "Laguna" + }, + { + "code": "1498-5", + "display": "Nambe", + "definition": "Nambe" + }, + { + "code": "1499-3", + "display": "Picuris", + "definition": "Picuris" + }, + { + "code": "1500-8", + "display": "Piro", + "definition": "Piro" + }, + { + "code": "1501-6", + "display": "Pojoaque", + "definition": "Pojoaque" + }, + { + "code": "1502-4", + "display": "San Felipe", + "definition": "San Felipe" + }, + { + "code": "1503-2", + "display": "San Ildefonso", + "definition": "San Ildefonso" + }, + { + "code": "1504-0", + "display": "San Juan Pueblo", + "definition": "San Juan Pueblo" + }, + { + "code": "1505-7", + "display": "San Juan De", + "definition": "San Juan De" + }, + { + "code": "1506-5", + "display": "San Juan", + "definition": "San Juan" + }, + { + "code": "1507-3", + "display": "Sandia", + "definition": "Sandia" + }, + { + "code": "1508-1", + "display": "Santa Ana", + "definition": "Santa Ana" + }, + { + "code": "1509-9", + "display": "Santa Clara", + "definition": "Santa Clara" + }, + { + "code": "1510-7", + "display": "Santo Domingo", + "definition": "Santo Domingo" + }, + { + "code": "1511-5", + "display": "Taos", + "definition": "Taos" + }, + { + "code": "1512-3", + "display": "Tesuque", + "definition": "Tesuque" + }, + { + "code": "1513-1", + "display": "Tewa", + "definition": "Tewa" + }, + { + "code": "1514-9", + "display": "Tigua", + "definition": "Tigua" + }, + { + "code": "1515-6", + "display": "Zia", + "definition": "Zia" + }, + { + "code": "1516-4", + "display": "Zuni", + "definition": "Zuni" + } + ] + }, + { + "code": "1518-0", + "display": "Puget Sound Salish", + "definition": "Puget Sound Salish", + "concept": [ + { + "code": "1519-8", + "display": "Duwamish", + "definition": "Duwamish" + }, + { + "code": "1520-6", + "display": "Kikiallus", + "definition": "Kikiallus" + }, + { + "code": "1521-4", + "display": "Lower Skagit", + "definition": "Lower Skagit" + }, + { + "code": "1522-2", + "display": "Muckleshoot", + "definition": "Muckleshoot" + }, + { + "code": "1523-0", + "display": "Nisqually", + "definition": "Nisqually" + }, + { + "code": "1524-8", + "display": "Nooksack", + "definition": "Nooksack" + }, + { + "code": "1525-5", + "display": "Port Madison", + "definition": "Port Madison" + }, + { + "code": "1526-3", + "display": "Puyallup", + "definition": "Puyallup" + }, + { + "code": "1527-1", + "display": "Samish", + "definition": "Samish" + }, + { + "code": "1528-9", + "display": "Sauk-Suiattle", + "definition": "Sauk-Suiattle" + }, + { + "code": "1529-7", + "display": "Skokomish", + "definition": "Skokomish" + }, + { + "code": "1530-5", + "display": "Skykomish", + "definition": "Skykomish" + }, + { + "code": "1531-3", + "display": "Snohomish", + "definition": "Snohomish" + }, + { + "code": "1532-1", + "display": "Snoqualmie", + "definition": "Snoqualmie" + }, + { + "code": "1533-9", + "display": "Squaxin Island", + "definition": "Squaxin Island" + }, + { + "code": "1534-7", + "display": "Steilacoom", + "definition": "Steilacoom" + }, + { + "code": "1535-4", + "display": "Stillaguamish", + "definition": "Stillaguamish" + }, + { + "code": "1536-2", + "display": "Suquamish", + "definition": "Suquamish" + }, + { + "code": "1537-0", + "display": "Swinomish", + "definition": "Swinomish" + }, + { + "code": "1538-8", + "display": "Tulalip", + "definition": "Tulalip" + }, + { + "code": "1539-6", + "display": "Upper Skagit", + "definition": "Upper Skagit" + } + ] + }, + { + "code": "1541-2", + "display": "Quapaw", + "definition": "Quapaw" + }, + { + "code": "1543-8", + "display": "Quinault", + "definition": "Quinault" + }, + { + "code": "1545-3", + "display": "Rappahannock", + "definition": "Rappahannock" + }, + { + "code": "1547-9", + "display": "Reno-Sparks", + "definition": "Reno-Sparks" + }, + { + "code": "1549-5", + "display": "Round Valley", + "definition": "Round Valley" + }, + { + "code": "1551-1", + "display": "Sac and Fox", + "definition": "Sac and Fox", + "concept": [ + { + "code": "1552-9", + "display": "Iowa Sac and Fox", + "definition": "Iowa Sac and Fox" + }, + { + "code": "1553-7", + "display": "Missouri Sac and Fox", + "definition": "Missouri Sac and Fox" + }, + { + "code": "1554-5", + "display": "Oklahoma Sac and Fox", + "definition": "Oklahoma Sac and Fox" + } + ] + }, + { + "code": "1556-0", + "display": "Salinan", + "definition": "Salinan" + }, + { + "code": "1558-6", + "display": "Salish", + "definition": "Salish" + }, + { + "code": "1560-2", + "display": "Salish and Kootenai", + "definition": "Salish and Kootenai" + }, + { + "code": "1562-8", + "display": "Schaghticoke", + "definition": "Schaghticoke" + }, + { + "code": "1564-4", + "display": "Scott Valley", + "definition": "Scott Valley" + }, + { + "code": "1566-9", + "display": "Seminole", + "definition": "Seminole", + "concept": [ + { + "code": "1567-7", + "display": "Big Cypress", + "definition": "Big Cypress" + }, + { + "code": "1568-5", + "display": "Brighton", + "definition": "Brighton" + }, + { + "code": "1569-3", + "display": "Florida Seminole", + "definition": "Florida Seminole" + }, + { + "code": "1570-1", + "display": "Hollywood Seminole", + "definition": "Hollywood Seminole" + }, + { + "code": "1571-9", + "display": "Oklahoma Seminole", + "definition": "Oklahoma Seminole" + } + ] + }, + { + "code": "1573-5", + "display": "Serrano", + "definition": "Serrano", + "concept": [ + { + "code": "1574-3", + "display": "San Manual", + "definition": "San Manual" + } + ] + }, + { + "code": "1576-8", + "display": "Shasta", + "definition": "Shasta" + }, + { + "code": "1578-4", + "display": "Shawnee", + "definition": "Shawnee", + "concept": [ + { + "code": "1579-2", + "display": "Absentee Shawnee", + "definition": "Absentee Shawnee" + }, + { + "code": "1580-0", + "display": "Eastern Shawnee", + "definition": "Eastern Shawnee" + } + ] + }, + { + "code": "1582-6", + "display": "Shinnecock", + "definition": "Shinnecock" + }, + { + "code": "1584-2", + "display": "Shoalwater Bay", + "definition": "Shoalwater Bay" + }, + { + "code": "1586-7", + "display": "Shoshone", + "definition": "Shoshone", + "concept": [ + { + "code": "1587-5", + "display": "Battle Mountain", + "definition": "Battle Mountain" + }, + { + "code": "1588-3", + "display": "Duckwater", + "definition": "Duckwater" + }, + { + "code": "1589-1", + "display": "Elko", + "definition": "Elko" + }, + { + "code": "1590-9", + "display": "Ely", + "definition": "Ely" + }, + { + "code": "1591-7", + "display": "Goshute", + "definition": "Goshute" + }, + { + "code": "1592-5", + "display": "Panamint", + "definition": "Panamint" + }, + { + "code": "1593-3", + "display": "Ruby Valley", + "definition": "Ruby Valley" + }, + { + "code": "1594-1", + "display": "Skull Valley", + "definition": "Skull Valley" + }, + { + "code": "1595-8", + "display": "South Fork Shoshone", + "definition": "South Fork Shoshone" + }, + { + "code": "1596-6", + "display": "Te-Moak Western Shoshone", + "definition": "Te-Moak Western Shoshone" + }, + { + "code": "1597-4", + "display": "Timbi-Sha Shoshone", + "definition": "Timbi-Sha Shoshone" + }, + { + "code": "1598-2", + "display": "Washakie", + "definition": "Washakie" + }, + { + "code": "1599-0", + "display": "Wind River Shoshone", + "definition": "Wind River Shoshone" + }, + { + "code": "1600-6", + "display": "Yomba", + "definition": "Yomba" + } + ] + }, + { + "code": "1602-2", + "display": "Shoshone Paiute", + "definition": "Shoshone Paiute", + "concept": [ + { + "code": "1603-0", + "display": "Duck Valley", + "definition": "Duck Valley" + }, + { + "code": "1604-8", + "display": "Fallon", + "definition": "Fallon" + }, + { + "code": "1605-5", + "display": "Fort McDermitt", + "definition": "Fort McDermitt" + } + ] + }, + { + "code": "1607-1", + "display": "Siletz", + "definition": "Siletz" + }, + { + "code": "1609-7", + "display": "Sioux", + "definition": "Sioux", + "concept": [ + { + "code": "1610-5", + "display": "Blackfoot Sioux", + "definition": "Blackfoot Sioux" + }, + { + "code": "1611-3", + "display": "Brule Sioux", + "definition": "Brule Sioux" + }, + { + "code": "1612-1", + "display": "Cheyenne River Sioux", + "definition": "Cheyenne River Sioux" + }, + { + "code": "1613-9", + "display": "Crow Creek Sioux", + "definition": "Crow Creek Sioux" + }, + { + "code": "1614-7", + "display": "Dakota Sioux", + "definition": "Dakota Sioux" + }, + { + "code": "1615-4", + "display": "Flandreau Santee", + "definition": "Flandreau Santee" + }, + { + "code": "1616-2", + "display": "Fort Peck", + "definition": "Fort Peck" + }, + { + "code": "1617-0", + "display": "Lake Traverse Sioux", + "definition": "Lake Traverse Sioux" + }, + { + "code": "1618-8", + "display": "Lower Brule Sioux", + "definition": "Lower Brule Sioux" + }, + { + "code": "1619-6", + "display": "Lower Sioux", + "definition": "Lower Sioux" + }, + { + "code": "1620-4", + "display": "Mdewakanton Sioux", + "definition": "Mdewakanton Sioux" + }, + { + "code": "1621-2", + "display": "Miniconjou", + "definition": "Miniconjou" + }, + { + "code": "1622-0", + "display": "Oglala Sioux", + "definition": "Oglala Sioux" + }, + { + "code": "1623-8", + "display": "Pine Ridge Sioux", + "definition": "Pine Ridge Sioux" + }, + { + "code": "1624-6", + "display": "Pipestone Sioux", + "definition": "Pipestone Sioux" + }, + { + "code": "1625-3", + "display": "Prairie Island Sioux", + "definition": "Prairie Island Sioux" + }, + { + "code": "1626-1", + "display": "Prior Lake Sioux", + "definition": "Prior Lake Sioux" + }, + { + "code": "1627-9", + "display": "Rosebud Sioux", + "definition": "Rosebud Sioux" + }, + { + "code": "1628-7", + "display": "Sans Arc Sioux", + "definition": "Sans Arc Sioux" + }, + { + "code": "1629-5", + "display": "Santee Sioux", + "definition": "Santee Sioux" + }, + { + "code": "1630-3", + "display": "Sisseton-Wahpeton", + "definition": "Sisseton-Wahpeton" + }, + { + "code": "1631-1", + "display": "Sisseton Sioux", + "definition": "Sisseton Sioux" + }, + { + "code": "1632-9", + "display": "Spirit Lake Sioux", + "definition": "Spirit Lake Sioux" + }, + { + "code": "1633-7", + "display": "Standing Rock Sioux", + "definition": "Standing Rock Sioux" + }, + { + "code": "1634-5", + "display": "Teton Sioux", + "definition": "Teton Sioux" + }, + { + "code": "1635-2", + "display": "Two Kettle Sioux", + "definition": "Two Kettle Sioux" + }, + { + "code": "1636-0", + "display": "Upper Sioux", + "definition": "Upper Sioux" + }, + { + "code": "1637-8", + "display": "Wahpekute Sioux", + "definition": "Wahpekute Sioux" + }, + { + "code": "1638-6", + "display": "Wahpeton Sioux", + "definition": "Wahpeton Sioux" + }, + { + "code": "1639-4", + "display": "Wazhaza Sioux", + "definition": "Wazhaza Sioux" + }, + { + "code": "1640-2", + "display": "Yankton Sioux", + "definition": "Yankton Sioux" + }, + { + "code": "1641-0", + "display": "Yanktonai Sioux", + "definition": "Yanktonai Sioux" + } + ] + }, + { + "code": "1643-6", + "display": "Siuslaw", + "definition": "Siuslaw" + }, + { + "code": "1645-1", + "display": "Spokane", + "definition": "Spokane" + }, + { + "code": "1647-7", + "display": "Stewart", + "definition": "Stewart" + }, + { + "code": "1649-3", + "display": "Stockbridge", + "definition": "Stockbridge" + }, + { + "code": "1651-9", + "display": "Susanville", + "definition": "Susanville" + }, + { + "code": "1653-5", + "display": "Tohono O'Odham", + "definition": "Tohono O'Odham", + "concept": [ + { + "code": "1654-3", + "display": "Ak-Chin", + "definition": "Ak-Chin" + }, + { + "code": "1655-0", + "display": "Gila Bend", + "definition": "Gila Bend" + }, + { + "code": "1656-8", + "display": "San Xavier", + "definition": "San Xavier" + }, + { + "code": "1657-6", + "display": "Sells", + "definition": "Sells" + } + ] + }, + { + "code": "1659-2", + "display": "Tolowa", + "definition": "Tolowa" + }, + { + "code": "1661-8", + "display": "Tonkawa", + "definition": "Tonkawa" + }, + { + "code": "1663-4", + "display": "Tygh", + "definition": "Tygh" + }, + { + "code": "1665-9", + "display": "Umatilla", + "definition": "Umatilla" + }, + { + "code": "1667-5", + "display": "Umpqua", + "definition": "Umpqua", + "concept": [ + { + "code": "1668-3", + "display": "Cow Creek Umpqua", + "definition": "Cow Creek Umpqua" + } + ] + }, + { + "code": "1670-9", + "display": "Ute", + "definition": "Ute", + "concept": [ + { + "code": "1671-7", + "display": "Allen Canyon", + "definition": "Allen Canyon" + }, + { + "code": "1672-5", + "display": "Uintah Ute", + "definition": "Uintah Ute" + }, + { + "code": "1673-3", + "display": "Ute Mountain Ute", + "definition": "Ute Mountain Ute" + } + ] + }, + { + "code": "1675-8", + "display": "Wailaki", + "definition": "Wailaki" + }, + { + "code": "1677-4", + "display": "Walla-Walla", + "definition": "Walla-Walla" + }, + { + "code": "1679-0", + "display": "Wampanoag", + "definition": "Wampanoag", + "concept": [ + { + "code": "1680-8", + "display": "Gay Head Wampanoag", + "definition": "Gay Head Wampanoag" + }, + { + "code": "1681-6", + "display": "Mashpee Wampanoag", + "definition": "Mashpee Wampanoag" + } + ] + }, + { + "code": "1683-2", + "display": "Warm Springs", + "definition": "Warm Springs" + }, + { + "code": "1685-7", + "display": "Wascopum", + "definition": "Wascopum" + }, + { + "code": "1687-3", + "display": "Washoe", + "definition": "Washoe", + "concept": [ + { + "code": "1688-1", + "display": "Alpine", + "definition": "Alpine" + }, + { + "code": "1689-9", + "display": "Carson", + "definition": "Carson" + }, + { + "code": "1690-7", + "display": "Dresslerville", + "definition": "Dresslerville" + } + ] + }, + { + "code": "1692-3", + "display": "Wichita", + "definition": "Wichita" + }, + { + "code": "1694-9", + "display": "Wind River", + "definition": "Wind River" + }, + { + "code": "1696-4", + "display": "Winnebago", + "definition": "Winnebago", + "concept": [ + { + "code": "1697-2", + "display": "Ho-chunk", + "definition": "Ho-chunk" + }, + { + "code": "1698-0", + "display": "Nebraska Winnebago", + "definition": "Nebraska Winnebago" + } + ] + }, + { + "code": "1700-4", + "display": "Winnemucca", + "definition": "Winnemucca" + }, + { + "code": "1702-0", + "display": "Wintun", + "definition": "Wintun" + }, + { + "code": "1704-6", + "display": "Wiyot", + "definition": "Wiyot", + "concept": [ + { + "code": "1705-3", + "display": "Table Bluff", + "definition": "Table Bluff" + } + ] + }, + { + "code": "1707-9", + "display": "Yakama", + "definition": "Yakama" + }, + { + "code": "1709-5", + "display": "Yakama Cowlitz", + "definition": "Yakama Cowlitz" + }, + { + "code": "1711-1", + "display": "Yaqui", + "definition": "Yaqui", + "concept": [ + { + "code": "1712-9", + "display": "Barrio Libre", + "definition": "Barrio Libre" + }, + { + "code": "1713-7", + "display": "Pascua Yaqui", + "definition": "Pascua Yaqui" + } + ] + }, + { + "code": "1715-2", + "display": "Yavapai Apache", + "definition": "Yavapai Apache" + }, + { + "code": "1717-8", + "display": "Yokuts", + "definition": "Yokuts", + "concept": [ + { + "code": "1718-6", + "display": "Chukchansi", + "definition": "Chukchansi" + }, + { + "code": "1719-4", + "display": "Tachi", + "definition": "Tachi" + }, + { + "code": "1720-2", + "display": "Tule River", + "definition": "Tule River" + } + ] + }, + { + "code": "1722-8", + "display": "Yuchi", + "definition": "Yuchi" + }, + { + "code": "1724-4", + "display": "Yuman", + "definition": "Yuman", + "concept": [ + { + "code": "1725-1", + "display": "Cocopah", + "definition": "Cocopah" + }, + { + "code": "1726-9", + "display": "Havasupai", + "definition": "Havasupai" + }, + { + "code": "1727-7", + "display": "Hualapai", + "definition": "Hualapai" + }, + { + "code": "1728-5", + "display": "Maricopa", + "definition": "Maricopa" + }, + { + "code": "1729-3", + "display": "Mohave", + "definition": "Mohave" + }, + { + "code": "1730-1", + "display": "Quechan", + "definition": "Quechan" + }, + { + "code": "1731-9", + "display": "Yavapai", + "definition": "Yavapai" + } + ] + }, + { + "code": "1732-7", + "display": "Yurok", + "definition": "Yurok", + "concept": [ + { + "code": "1733-5", + "display": "Coast Yurok", + "definition": "Coast Yurok" + } + ] + } + ] + }, + { + "code": "1735-0", + "display": "Alaska Native", + "definition": "Alaska Native", + "concept": [ + { + "code": "1737-6", + "display": "Alaska Indian", + "definition": "Alaska Indian", + "concept": [ + { + "code": "1739-2", + "display": "Alaskan Athabascan", + "definition": "Alaskan Athabascan", + "concept": [ + { + "code": "1740-0", + "display": "Ahtna", + "definition": "Ahtna" + } + ] + }, + { + "code": "1811-9", + "display": "Southeast Alaska", + "definition": "Southeast Alaska", + "concept": [ + { + "code": "1813-5", + "display": "Tlingit-Haida", + "definition": "Tlingit-Haida", + "concept": [ + { + "code": "1814-3", + "display": "Angoon", + "definition": "Angoon" + }, + { + "code": "1815-0", + "display": "Central Council of Tlingit and Haida Tribes", + "definition": "Central Council of Tlingit and Haida Tribes" + }, + { + "code": "1816-8", + "display": "Chilkat", + "definition": "Chilkat" + }, + { + "code": "1817-6", + "display": "Chilkoot", + "definition": "Chilkoot" + }, + { + "code": "1818-4", + "display": "Craig", + "definition": "Craig" + }, + { + "code": "1819-2", + "display": "Douglas", + "definition": "Douglas" + }, + { + "code": "1820-0", + "display": "Haida", + "definition": "Haida" + }, + { + "code": "1821-8", + "display": "Hoonah", + "definition": "Hoonah" + }, + { + "code": "1822-6", + "display": "Hydaburg", + "definition": "Hydaburg" + }, + { + "code": "1823-4", + "display": "Kake", + "definition": "Kake" + }, + { + "code": "1824-2", + "display": "Kasaan", + "definition": "Kasaan" + }, + { + "code": "1825-9", + "display": "Kenaitze", + "definition": "Kenaitze" + }, + { + "code": "1826-7", + "display": "Ketchikan", + "definition": "Ketchikan" + }, + { + "code": "1827-5", + "display": "Klawock", + "definition": "Klawock" + }, + { + "code": "1828-3", + "display": "Pelican", + "definition": "Pelican" + }, + { + "code": "1829-1", + "display": "Petersburg", + "definition": "Petersburg" + }, + { + "code": "1830-9", + "display": "Saxman", + "definition": "Saxman" + }, + { + "code": "1831-7", + "display": "Sitka", + "definition": "Sitka" + }, + { + "code": "1832-5", + "display": "Tenakee Springs", + "definition": "Tenakee Springs" + }, + { + "code": "1833-3", + "display": "Tlingit", + "definition": "Tlingit" + }, + { + "code": "1834-1", + "display": "Wrangell", + "definition": "Wrangell" + }, + { + "code": "1835-8", + "display": "Yakutat", + "definition": "Yakutat" + } + ] + }, + { + "code": "1837-4", + "display": "Tsimshian", + "definition": "Tsimshian", + "concept": [ + { + "code": "1838-2", + "display": "Metlakatla", + "definition": "Metlakatla" + } + ] + } + ] + } + ] + }, + { + "code": "1840-8", + "display": "Eskimo", + "definition": "Eskimo", + "concept": [ + { + "code": "1842-4", + "display": "Greenland Eskimo", + "definition": "Greenland Eskimo" + }, + { + "code": "1844-0", + "display": "Inupiat Eskimo", + "definition": "Inupiat Eskimo", + "concept": [ + { + "code": "1845-7", + "display": "Ambler", + "definition": "Ambler" + }, + { + "code": "1846-5", + "display": "Anaktuvuk", + "definition": "Anaktuvuk" + }, + { + "code": "1847-3", + "display": "Anaktuvuk Pass", + "definition": "Anaktuvuk Pass" + }, + { + "code": "1848-1", + "display": "Arctic Slope Inupiat", + "definition": "Arctic Slope Inupiat" + }, + { + "code": "1849-9", + "display": "Arctic Slope Corporation", + "definition": "Arctic Slope Corporation" + }, + { + "code": "1850-7", + "display": "Atqasuk", + "definition": "Atqasuk" + }, + { + "code": "1851-5", + "display": "Barrow", + "definition": "Barrow" + }, + { + "code": "1852-3", + "display": "Bering Straits Inupiat", + "definition": "Bering Straits Inupiat" + }, + { + "code": "1853-1", + "display": "Brevig Mission", + "definition": "Brevig Mission" + }, + { + "code": "1854-9", + "display": "Buckland", + "definition": "Buckland" + }, + { + "code": "1855-6", + "display": "Chinik", + "definition": "Chinik" + }, + { + "code": "1856-4", + "display": "Council", + "definition": "Council" + }, + { + "code": "1857-2", + "display": "Deering", + "definition": "Deering" + }, + { + "code": "1858-0", + "display": "Elim", + "definition": "Elim" + }, + { + "code": "1859-8", + "display": "Golovin", + "definition": "Golovin" + }, + { + "code": "1860-6", + "display": "Inalik Diomede", + "definition": "Inalik Diomede" + }, + { + "code": "1861-4", + "display": "Inupiaq", + "definition": "Inupiaq" + }, + { + "code": "1862-2", + "display": "Kaktovik", + "definition": "Kaktovik" + }, + { + "code": "1863-0", + "display": "Kawerak", + "definition": "Kawerak" + }, + { + "code": "1864-8", + "display": "Kiana", + "definition": "Kiana" + }, + { + "code": "1865-5", + "display": "Kivalina", + "definition": "Kivalina" + }, + { + "code": "1866-3", + "display": "Kobuk", + "definition": "Kobuk" + }, + { + "code": "1867-1", + "display": "Kotzebue", + "definition": "Kotzebue" + }, + { + "code": "1868-9", + "display": "Koyuk", + "definition": "Koyuk" + }, + { + "code": "1869-7", + "display": "Kwiguk", + "definition": "Kwiguk" + }, + { + "code": "1870-5", + "display": "Mauneluk Inupiat", + "definition": "Mauneluk Inupiat" + }, + { + "code": "1871-3", + "display": "Nana Inupiat", + "definition": "Nana Inupiat" + }, + { + "code": "1872-1", + "display": "Noatak", + "definition": "Noatak" + }, + { + "code": "1873-9", + "display": "Nome", + "definition": "Nome" + }, + { + "code": "1874-7", + "display": "Noorvik", + "definition": "Noorvik" + }, + { + "code": "1875-4", + "display": "Nuiqsut", + "definition": "Nuiqsut" + }, + { + "code": "1876-2", + "display": "Point Hope", + "definition": "Point Hope" + }, + { + "code": "1877-0", + "display": "Point Lay", + "definition": "Point Lay" + }, + { + "code": "1878-8", + "display": "Selawik", + "definition": "Selawik" + }, + { + "code": "1879-6", + "display": "Shaktoolik", + "definition": "Shaktoolik" + }, + { + "code": "1880-4", + "display": "Shishmaref", + "definition": "Shishmaref" + }, + { + "code": "1881-2", + "display": "Shungnak", + "definition": "Shungnak" + }, + { + "code": "1882-0", + "display": "Solomon", + "definition": "Solomon" + }, + { + "code": "1883-8", + "display": "Teller", + "definition": "Teller" + }, + { + "code": "1884-6", + "display": "Unalakleet", + "definition": "Unalakleet" + }, + { + "code": "1885-3", + "display": "Wainwright", + "definition": "Wainwright" + }, + { + "code": "1886-1", + "display": "Wales", + "definition": "Wales" + }, + { + "code": "1887-9", + "display": "White Mountain", + "definition": "White Mountain" + }, + { + "code": "1888-7", + "display": "White Mountain Inupiat", + "definition": "White Mountain Inupiat" + }, + { + "code": "1889-5", + "display": "Mary's Igloo", + "definition": "Mary's Igloo" + } + ] + }, + { + "code": "1891-1", + "display": "Siberian Eskimo", + "definition": "Siberian Eskimo", + "concept": [ + { + "code": "1892-9", + "display": "Gambell", + "definition": "Gambell" + }, + { + "code": "1893-7", + "display": "Savoonga", + "definition": "Savoonga" + }, + { + "code": "1894-5", + "display": "Siberian Yupik", + "definition": "Siberian Yupik" + } + ] + }, + { + "code": "1896-0", + "display": "Yupik Eskimo", + "definition": "Yupik Eskimo", + "concept": [ + { + "code": "1897-8", + "display": "Akiachak", + "definition": "Akiachak" + }, + { + "code": "1898-6", + "display": "Akiak", + "definition": "Akiak" + }, + { + "code": "1899-4", + "display": "Alakanuk", + "definition": "Alakanuk" + }, + { + "code": "1900-0", + "display": "Aleknagik", + "definition": "Aleknagik" + }, + { + "code": "1901-8", + "display": "Andreafsky", + "definition": "Andreafsky" + }, + { + "code": "1902-6", + "display": "Aniak", + "definition": "Aniak" + }, + { + "code": "1903-4", + "display": "Atmautluak", + "definition": "Atmautluak" + }, + { + "code": "1904-2", + "display": "Bethel", + "definition": "Bethel" + }, + { + "code": "1905-9", + "display": "Bill Moore's Slough", + "definition": "Bill Moore's Slough" + }, + { + "code": "1906-7", + "display": "Bristol Bay Yupik", + "definition": "Bristol Bay Yupik" + }, + { + "code": "1907-5", + "display": "Calista Yupik", + "definition": "Calista Yupik" + }, + { + "code": "1908-3", + "display": "Chefornak", + "definition": "Chefornak" + }, + { + "code": "1909-1", + "display": "Chevak", + "definition": "Chevak" + }, + { + "code": "1910-9", + "display": "Chuathbaluk", + "definition": "Chuathbaluk" + }, + { + "code": "1911-7", + "display": "Clark's Point", + "definition": "Clark's Point" + }, + { + "code": "1912-5", + "display": "Crooked Creek", + "definition": "Crooked Creek" + }, + { + "code": "1913-3", + "display": "Dillingham", + "definition": "Dillingham" + }, + { + "code": "1914-1", + "display": "Eek", + "definition": "Eek" + }, + { + "code": "1915-8", + "display": "Ekuk", + "definition": "Ekuk" + }, + { + "code": "1916-6", + "display": "Ekwok", + "definition": "Ekwok" + }, + { + "code": "1917-4", + "display": "Emmonak", + "definition": "Emmonak" + }, + { + "code": "1918-2", + "display": "Goodnews Bay", + "definition": "Goodnews Bay" + }, + { + "code": "1919-0", + "display": "Hooper Bay", + "definition": "Hooper Bay" + }, + { + "code": "1920-8", + "display": "Iqurmuit (Russian Mission)", + "definition": "Iqurmuit (Russian Mission)" + }, + { + "code": "1921-6", + "display": "Kalskag", + "definition": "Kalskag" + }, + { + "code": "1922-4", + "display": "Kasigluk", + "definition": "Kasigluk" + }, + { + "code": "1923-2", + "display": "Kipnuk", + "definition": "Kipnuk" + }, + { + "code": "1924-0", + "display": "Koliganek", + "definition": "Koliganek" + }, + { + "code": "1925-7", + "display": "Kongiganak", + "definition": "Kongiganak" + }, + { + "code": "1926-5", + "display": "Kotlik", + "definition": "Kotlik" + }, + { + "code": "1927-3", + "display": "Kwethluk", + "definition": "Kwethluk" + }, + { + "code": "1928-1", + "display": "Kwigillingok", + "definition": "Kwigillingok" + }, + { + "code": "1929-9", + "display": "Levelock", + "definition": "Levelock" + }, + { + "code": "1930-7", + "display": "Lower Kalskag", + "definition": "Lower Kalskag" + }, + { + "code": "1931-5", + "display": "Manokotak", + "definition": "Manokotak" + }, + { + "code": "1932-3", + "display": "Marshall", + "definition": "Marshall" + }, + { + "code": "1933-1", + "display": "Mekoryuk", + "definition": "Mekoryuk" + }, + { + "code": "1934-9", + "display": "Mountain Village", + "definition": "Mountain Village" + }, + { + "code": "1935-6", + "display": "Naknek", + "definition": "Naknek" + }, + { + "code": "1936-4", + "display": "Napaumute", + "definition": "Napaumute" + }, + { + "code": "1937-2", + "display": "Napakiak", + "definition": "Napakiak" + }, + { + "code": "1938-0", + "display": "Napaskiak", + "definition": "Napaskiak" + }, + { + "code": "1939-8", + "display": "Newhalen", + "definition": "Newhalen" + }, + { + "code": "1940-6", + "display": "New Stuyahok", + "definition": "New Stuyahok" + }, + { + "code": "1941-4", + "display": "Newtok", + "definition": "Newtok" + }, + { + "code": "1942-2", + "display": "Nightmute", + "definition": "Nightmute" + }, + { + "code": "1943-0", + "display": "Nunapitchukv", + "definition": "Nunapitchukv" + }, + { + "code": "1944-8", + "display": "Oscarville", + "definition": "Oscarville" + }, + { + "code": "1945-5", + "display": "Pilot Station", + "definition": "Pilot Station" + }, + { + "code": "1946-3", + "display": "Pitkas Point", + "definition": "Pitkas Point" + }, + { + "code": "1947-1", + "display": "Platinum", + "definition": "Platinum" + }, + { + "code": "1948-9", + "display": "Portage Creek", + "definition": "Portage Creek" + }, + { + "code": "1949-7", + "display": "Quinhagak", + "definition": "Quinhagak" + }, + { + "code": "1950-5", + "display": "Red Devil", + "definition": "Red Devil" + }, + { + "code": "1951-3", + "display": "St. Michael", + "definition": "St. Michael" + }, + { + "code": "1952-1", + "display": "Scammon Bay", + "definition": "Scammon Bay" + }, + { + "code": "1953-9", + "display": "Sheldon's Point", + "definition": "Sheldon's Point" + }, + { + "code": "1954-7", + "display": "Sleetmute", + "definition": "Sleetmute" + }, + { + "code": "1955-4", + "display": "Stebbins", + "definition": "Stebbins" + }, + { + "code": "1956-2", + "display": "Togiak", + "definition": "Togiak" + }, + { + "code": "1957-0", + "display": "Toksook", + "definition": "Toksook" + }, + { + "code": "1958-8", + "display": "Tulukskak", + "definition": "Tulukskak" + }, + { + "code": "1959-6", + "display": "Tuntutuliak", + "definition": "Tuntutuliak" + }, + { + "code": "1960-4", + "display": "Tununak", + "definition": "Tununak" + }, + { + "code": "1961-2", + "display": "Twin Hills", + "definition": "Twin Hills" + }, + { + "code": "1962-0", + "display": "Georgetown", + "definition": "Georgetown" + }, + { + "code": "1963-8", + "display": "St. Mary's", + "definition": "St. Mary's" + }, + { + "code": "1964-6", + "display": "Umkumiate", + "definition": "Umkumiate" + } + ] + } + ] + }, + { + "code": "1966-1", + "display": "Aleut", + "definition": "Aleut", + "concept": [ + { + "code": "1968-7", + "display": "Alutiiq Aleut", + "definition": "Alutiiq Aleut", + "concept": [ + { + "code": "1969-5", + "display": "Tatitlek", + "definition": "Tatitlek" + }, + { + "code": "1970-3", + "display": "Ugashik", + "definition": "Ugashik" + } + ] + }, + { + "code": "1972-9", + "display": "Bristol Bay Aleut", + "definition": "Bristol Bay Aleut", + "concept": [ + { + "code": "1973-7", + "display": "Chignik", + "definition": "Chignik" + }, + { + "code": "1974-5", + "display": "Chignik Lake", + "definition": "Chignik Lake" + }, + { + "code": "1975-2", + "display": "Egegik", + "definition": "Egegik" + }, + { + "code": "1976-0", + "display": "Igiugig", + "definition": "Igiugig" + }, + { + "code": "1977-8", + "display": "Ivanof Bay", + "definition": "Ivanof Bay" + }, + { + "code": "1978-6", + "display": "King Salmon", + "definition": "King Salmon" + }, + { + "code": "1979-4", + "display": "Kokhanok", + "definition": "Kokhanok" + }, + { + "code": "1980-2", + "display": "Perryville", + "definition": "Perryville" + }, + { + "code": "1981-0", + "display": "Pilot Point", + "definition": "Pilot Point" + }, + { + "code": "1982-8", + "display": "Port Heiden", + "definition": "Port Heiden" + } + ] + }, + { + "code": "1984-4", + "display": "Chugach Aleut", + "definition": "Chugach Aleut", + "concept": [ + { + "code": "1985-1", + "display": "Chenega", + "definition": "Chenega" + }, + { + "code": "1986-9", + "display": "Chugach Corporation", + "definition": "Chugach Corporation" + }, + { + "code": "1987-7", + "display": "English Bay", + "definition": "English Bay" + }, + { + "code": "1988-5", + "display": "Port Graham", + "definition": "Port Graham" + } + ] + }, + { + "code": "1990-1", + "display": "Eyak", + "definition": "Eyak" + }, + { + "code": "1992-7", + "display": "Koniag Aleut", + "definition": "Koniag Aleut", + "concept": [ + { + "code": "1993-5", + "display": "Akhiok", + "definition": "Akhiok" + }, + { + "code": "1994-3", + "display": "Agdaagux", + "definition": "Agdaagux" + }, + { + "code": "1995-0", + "display": "Karluk", + "definition": "Karluk" + }, + { + "code": "1996-8", + "display": "Kodiak", + "definition": "Kodiak" + }, + { + "code": "1997-6", + "display": "Larsen Bay", + "definition": "Larsen Bay" + }, + { + "code": "1998-4", + "display": "Old Harbor", + "definition": "Old Harbor" + }, + { + "code": "1999-2", + "display": "Ouzinkie", + "definition": "Ouzinkie" + }, + { + "code": "2000-8", + "display": "Port Lions", + "definition": "Port Lions" + } + ] + }, + { + "code": "2002-4", + "display": "Sugpiaq", + "definition": "Sugpiaq" + }, + { + "code": "2004-0", + "display": "Suqpigaq", + "definition": "Suqpigaq" + }, + { + "code": "2006-5", + "display": "Unangan Aleut", + "definition": "Unangan Aleut", + "concept": [ + { + "code": "2007-3", + "display": "Akutan", + "definition": "Akutan" + }, + { + "code": "2008-1", + "display": "Aleut Corporation", + "definition": "Aleut Corporation" + }, + { + "code": "2009-9", + "display": "Aleutian", + "definition": "Aleutian" + }, + { + "code": "2010-7", + "display": "Aleutian Islander", + "definition": "Aleutian Islander" + }, + { + "code": "2011-5", + "display": "Atka", + "definition": "Atka" + }, + { + "code": "2012-3", + "display": "Belkofski", + "definition": "Belkofski" + }, + { + "code": "2013-1", + "display": "Chignik Lagoon", + "definition": "Chignik Lagoon" + }, + { + "code": "2014-9", + "display": "King Cove", + "definition": "King Cove" + }, + { + "code": "2015-6", + "display": "False Pass", + "definition": "False Pass" + }, + { + "code": "2016-4", + "display": "Nelson Lagoon", + "definition": "Nelson Lagoon" + }, + { + "code": "2017-2", + "display": "Nikolski", + "definition": "Nikolski" + }, + { + "code": "2018-0", + "display": "Pauloff Harbor", + "definition": "Pauloff Harbor" + }, + { + "code": "2019-8", + "display": "Qagan Toyagungin", + "definition": "Qagan Toyagungin" + }, + { + "code": "2020-6", + "display": "Qawalangin", + "definition": "Qawalangin" + }, + { + "code": "2021-4", + "display": "St. George", + "definition": "St. George" + }, + { + "code": "2022-2", + "display": "St. Paul", + "definition": "St. Paul" + }, + { + "code": "2023-0", + "display": "Sand Point", + "definition": "Sand Point" + }, + { + "code": "2024-8", + "display": "South Naknek", + "definition": "South Naknek" + }, + { + "code": "2025-5", + "display": "Unalaska", + "definition": "Unalaska" + }, + { + "code": "2026-3", + "display": "Unga", + "definition": "Unga" + } + ] + } + ] + } + ] + } + ] + }, + { + "code": "2028-9", + "display": "Asian", + "definition": "Asian", + "concept": [ + { + "code": "2029-7", + "display": "Asian Indian", + "definition": "Asian Indian" + }, + { + "code": "2030-5", + "display": "Bangladeshi", + "definition": "Bangladeshi" + }, + { + "code": "2031-3", + "display": "Bhutanese", + "definition": "Bhutanese" + }, + { + "code": "2032-1", + "display": "Burmese", + "definition": "Burmese" + }, + { + "code": "2033-9", + "display": "Cambodian", + "definition": "Cambodian" + }, + { + "code": "2034-7", + "display": "Chinese", + "definition": "Chinese" + }, + { + "code": "2035-4", + "display": "Taiwanese", + "definition": "Taiwanese" + }, + { + "code": "2036-2", + "display": "Filipino", + "definition": "Filipino" + }, + { + "code": "2037-0", + "display": "Hmong", + "definition": "Hmong" + }, + { + "code": "2038-8", + "display": "Indonesian", + "definition": "Indonesian" + }, + { + "code": "2039-6", + "display": "Japanese", + "definition": "Japanese" + }, + { + "code": "2040-4", + "display": "Korean", + "definition": "Korean" + }, + { + "code": "2041-2", + "display": "Laotian", + "definition": "Laotian" + }, + { + "code": "2042-0", + "display": "Malaysian", + "definition": "Malaysian" + }, + { + "code": "2043-8", + "display": "Okinawan", + "definition": "Okinawan" + }, + { + "code": "2044-6", + "display": "Pakistani", + "definition": "Pakistani" + }, + { + "code": "2045-3", + "display": "Sri Lankan", + "definition": "Sri Lankan" + }, + { + "code": "2046-1", + "display": "Thai", + "definition": "Thai" + }, + { + "code": "2047-9", + "display": "Vietnamese", + "definition": "Vietnamese" + }, + { + "code": "2048-7", + "display": "Iwo Jiman", + "definition": "Iwo Jiman" + }, + { + "code": "2049-5", + "display": "Maldivian", + "definition": "Maldivian" + }, + { + "code": "2050-3", + "display": "Nepalese", + "definition": "Nepalese" + }, + { + "code": "2051-1", + "display": "Singaporean", + "definition": "Singaporean" + }, + { + "code": "2052-9", + "display": "Madagascar", + "definition": "Madagascar" + } + ] + }, + { + "code": "2054-5", + "display": "Black or African American", + "definition": "Black or African American", + "concept": [ + { + "code": "2056-0", + "display": "Black", + "definition": "Black" + }, + { + "code": "2058-6", + "display": "African American", + "definition": "African American" + }, + { + "code": "2060-2", + "display": "African", + "definition": "African", + "concept": [ + { + "code": "2061-0", + "display": "Botswanan", + "definition": "Botswanan" + }, + { + "code": "2062-8", + "display": "Ethiopian", + "definition": "Ethiopian" + }, + { + "code": "2063-6", + "display": "Liberian", + "definition": "Liberian" + }, + { + "code": "2064-4", + "display": "Namibian", + "definition": "Namibian" + }, + { + "code": "2065-1", + "display": "Nigerian", + "definition": "Nigerian" + }, + { + "code": "2066-9", + "display": "Zairean", + "definition": "Zairean" + } + ] + }, + { + "code": "2067-7", + "display": "Bahamian", + "definition": "Bahamian" + }, + { + "code": "2068-5", + "display": "Barbadian", + "definition": "Barbadian" + }, + { + "code": "2069-3", + "display": "Dominican", + "definition": "Dominican" + }, + { + "code": "2070-1", + "display": "Dominica Islander", + "definition": "Dominica Islander" + }, + { + "code": "2071-9", + "display": "Haitian", + "definition": "Haitian" + }, + { + "code": "2072-7", + "display": "Jamaican", + "definition": "Jamaican" + }, + { + "code": "2073-5", + "display": "Tobagoan", + "definition": "Tobagoan" + }, + { + "code": "2074-3", + "display": "Trinidadian", + "definition": "Trinidadian" + }, + { + "code": "2075-0", + "display": "West Indian", + "definition": "West Indian" + } + ] + }, + { + "code": "2076-8", + "display": "Native Hawaiian or Other Pacific Islander", + "definition": "Native Hawaiian or Other Pacific Islander", + "concept": [ + { + "code": "2078-4", + "display": "Polynesian", + "definition": "Polynesian", + "concept": [ + { + "code": "2079-2", + "display": "Native Hawaiian", + "definition": "Native Hawaiian" + }, + { + "code": "2080-0", + "display": "Samoan", + "definition": "Samoan" + }, + { + "code": "2081-8", + "display": "Tahitian", + "definition": "Tahitian" + }, + { + "code": "2082-6", + "display": "Tongan", + "definition": "Tongan" + }, + { + "code": "2083-4", + "display": "Tokelauan", + "definition": "Tokelauan" + } + ] + }, + { + "code": "2085-9", + "display": "Micronesian", + "definition": "Micronesian", + "concept": [ + { + "code": "2086-7", + "display": "Guamanian or Chamorro", + "definition": "Guamanian or Chamorro" + }, + { + "code": "2087-5", + "display": "Guamanian", + "definition": "Guamanian" + }, + { + "code": "2088-3", + "display": "Chamorro", + "definition": "Chamorro" + }, + { + "code": "2089-1", + "display": "Mariana Islander", + "definition": "Mariana Islander" + }, + { + "code": "2090-9", + "display": "Marshallese", + "definition": "Marshallese" + }, + { + "code": "2091-7", + "display": "Palauan", + "definition": "Palauan" + }, + { + "code": "2092-5", + "display": "Carolinian", + "definition": "Carolinian" + }, + { + "code": "2093-3", + "display": "Kosraean", + "definition": "Kosraean" + }, + { + "code": "2094-1", + "display": "Pohnpeian", + "definition": "Pohnpeian" + }, + { + "code": "2095-8", + "display": "Saipanese", + "definition": "Saipanese" + }, + { + "code": "2096-6", + "display": "Kiribati", + "definition": "Kiribati" + }, + { + "code": "2097-4", + "display": "Chuukese", + "definition": "Chuukese" + }, + { + "code": "2098-2", + "display": "Yapese", + "definition": "Yapese" + } + ] + }, + { + "code": "2100-6", + "display": "Melanesian", + "definition": "Melanesian", + "concept": [ + { + "code": "2101-4", + "display": "Fijian", + "definition": "Fijian" + }, + { + "code": "2102-2", + "display": "Papua New Guinean", + "definition": "Papua New Guinean" + }, + { + "code": "2103-0", + "display": "Solomon Islander", + "definition": "Solomon Islander" + }, + { + "code": "2104-8", + "display": "New Hebrides", + "definition": "New Hebrides" + } + ] + }, + { + "code": "2500-7", + "display": "Other Pacific Islander", + "definition": "Note that this term remains in the table for completeness, even though within HL7, the notion of Other code is deprecated." + } + ] + }, + { + "code": "2106-3", + "display": "White", + "definition": "White", + "concept": [ + { + "code": "2108-9", + "display": "European", + "definition": "European", + "concept": [ + { + "code": "2109-7", + "display": "Armenian", + "definition": "Armenian" + }, + { + "code": "2110-5", + "display": "English", + "definition": "English" + }, + { + "code": "2111-3", + "display": "French", + "definition": "French" + }, + { + "code": "2112-1", + "display": "German", + "definition": "German" + }, + { + "code": "2113-9", + "display": "Irish", + "definition": "Irish" + }, + { + "code": "2114-7", + "display": "Italian", + "definition": "Italian" + }, + { + "code": "2115-4", + "display": "Polish", + "definition": "Polish" + }, + { + "code": "2116-2", + "display": "Scottish", + "definition": "Scottish" + } + ] + }, + { + "code": "2118-8", + "display": "Middle Eastern or North African", + "definition": "Middle Eastern or North African", + "concept": [ + { + "code": "2119-6", + "display": "Assyrian", + "definition": "Assyrian" + }, + { + "code": "2120-4", + "display": "Egyptian", + "definition": "Egyptian" + }, + { + "code": "2121-2", + "display": "Iranian", + "definition": "Iranian" + }, + { + "code": "2122-0", + "display": "Iraqi", + "definition": "Iraqi" + }, + { + "code": "2123-8", + "display": "Lebanese", + "definition": "Lebanese" + }, + { + "code": "2124-6", + "display": "Palestinian", + "definition": "Palestinian" + }, + { + "code": "2125-3", + "display": "Syrian", + "definition": "Syrian" + }, + { + "code": "2126-1", + "display": "Afghanistani", + "definition": "Afghanistani" + }, + { + "code": "2127-9", + "display": "Israeili", + "definition": "Israeili" + } + ] + }, + { + "code": "2129-5", + "display": "Arab", + "definition": "Arab" + } + ] + }, + { + "code": "2131-1", + "display": "Other Race", + "definition": "Note that this term remains in the table for completeness, even though within HL7, the notion of Other code is deprecated." + } + ] +} \ No newline at end of file From 3e606dd9af25cd95ab12f78051b0201b6a3df2b5 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Fri, 19 Apr 2024 12:07:50 -0700 Subject: [PATCH 116/143] remove useless empty Coding, which was generating a warning on import. --- portal/config/eproms/Coding.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/portal/config/eproms/Coding.json b/portal/config/eproms/Coding.json index dfc712588..9c3f28fe6 100644 --- a/portal/config/eproms/Coding.json +++ b/portal/config/eproms/Coding.json @@ -5862,12 +5862,6 @@ "resourceType": "Coding", "system": "http://snomed.info/sct" }, - { - "code": "", - "display": "", - "resourceType": "Coding", - "system": "http://us.truenth.org/clinical-codes" - }, { "code": "111", "display": "biopsy", From 70ec17ab78d718d1f2860e1f446545bf1a5d0b1b Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Fri, 19 Apr 2024 16:32:37 -0700 Subject: [PATCH 117/143] frontend code fix - fix get EMPRO get triggers ajax call --- portal/static/js/src/modules/TnthAjax.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/portal/static/js/src/modules/TnthAjax.js b/portal/static/js/src/modules/TnthAjax.js index 593d73510..1d077ff3d 100644 --- a/portal/static/js/src/modules/TnthAjax.js +++ b/portal/static/js/src/modules/TnthAjax.js @@ -4,7 +4,7 @@ import tnthDates from "./TnthDate.js"; import SYSTEM_IDENTIFIER_ENUM from "./SYSTEM_IDENTIFIER_ENUM.js"; import CLINICAL_CODE_ENUM from "./CLINICAL_CODE_ENUM.js"; import Consent from "./Consent.js"; -import {DEFAULT_SERVER_DATA_ERROR, EPROMS_MAIN_STUDY_ID, EMPRO_TRIGGER_IN_PROCESS_STATE} from "../data/common/consts.js"; +import {DEFAULT_SERVER_DATA_ERROR, EPROMS_MAIN_STUDY_ID, EMPRO_TRIGGER_UNPROCCESSED_STATES} from "../data/common/consts.js"; const MAX_ATTEMPTS = 3 export default { /*global $ */ "beforeSend": function() { @@ -367,7 +367,7 @@ export default { /*global $ */ //if the trigger data has not been processed, try again until maximum number of attempts has been reached if (params.retryAttempt < params.maxTryAttempts && - dataState === EMPRO_TRIGGER_IN_PROCESS_STATE) { + EMPRO_TRIGGER_UNPROCCESSED_STATES.indexOf(dataState) !== -1) { params.retryAttempt++; setTimeout(function() { this.getSubStudyTriggers(userId, params, callback); From e0bb35fa46b3507da677159c73f03c0d48b79240 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Mon, 22 Apr 2024 10:46:01 -0700 Subject: [PATCH 118/143] correct logic around delay when waiting for user feedback on opt_out. --- portal/trigger_states/empro_states.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/portal/trigger_states/empro_states.py b/portal/trigger_states/empro_states.py index c9ca2bba6..dfabc783b 100644 --- a/portal/trigger_states/empro_states.py +++ b/portal/trigger_states/empro_states.py @@ -277,7 +277,8 @@ def delay_processing(ts): current_app.logger.debug(f"QQQ row timestamp: {ts.timestamp} now: {datetime.utcnow()}") current_app.logger.debug(f"QQQ row + delay {ts.timestamp + timedelta(seconds=OPT_OUT_DELAY)}") current_app.logger.debug(f"QQQ tzinfo: {ts.timestamp.tzinfo} tz2: {(ts.timestamp + timedelta(seconds=OPT_OUT_DELAY)).tzinfo} tz3: {datetime.utcnow().tzinfo}") - if ts.timestamp < timedelta(seconds=OPT_OUT_DELAY) + datetime.utcnow(): + assert((ts.timestamp + timedelta(seconds=OPT_OUT_DELAY)).tzinfo == datetime.utcnow().tzinfo) + if ts.timestamp + timedelta(seconds=OPT_OUT_DELAY) < datetime.utcnow(): current_app.logger.debug(f"QQQ return True from delay_processing") return True From 7e2f6d365aa21ba07603024887efa90f437d0db3 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Mon, 22 Apr 2024 10:53:58 -0700 Subject: [PATCH 119/143] when user opted out of some but not all domains, limit the list in the notification emails to clearly show the domains to be contacted about vs those the user requested be left alone. --- portal/trigger_states/empro_messages.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/portal/trigger_states/empro_messages.py b/portal/trigger_states/empro_messages.py index 3e424a311..c65223e23 100644 --- a/portal/trigger_states/empro_messages.py +++ b/portal/trigger_states/empro_messages.py @@ -99,14 +99,19 @@ def staff_emails(patient, hard_triggers, opted_out_domains, initial_notification if c.id not in staff_list_ids: staff_list.append(c) + # opt-in holds hard triggers the user did NOT opt-out of + opt_in_domains = hard_triggers + app_text_name = 'empro clinician trigger reminder' if initial_notification: app_text_name = 'empro clinician trigger notification' if not (set(hard_triggers) - set(opted_out_domains)): # All triggered were opted out of - pick up different email template app_text_name += " all opted out" + opt_in_domains = [] elif opted_out_domains: app_text_name += " partially opted out" + opt_in_domains = list(set(hard_triggers) - set(opted_out_domains)) # According to spec, args need at least: # - study ID @@ -134,7 +139,7 @@ def staff_emails(patient, hard_triggers, opted_out_domains, initial_notification label=_('View Participant Details'))) opted_out = ", ".join(opted_out_domains) if opted_out_domains else "" opted_out_display = "{opted_out}".format(opted_out=opted_out) - triggered_domains = ", ".join(hard_triggers) if hard_triggers else "" + triggered_domains = ", ".join(opt_in_domains) if opt_in_domains else "" triggered_domains_display = "{triggered_domains}".format( triggered_domains=triggered_domains) args = { From f020f24e85d321aeb48b648c0bbf6e9ca609752f Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Mon, 22 Apr 2024 12:28:56 -0700 Subject: [PATCH 120/143] debugging log stmts only --- portal/trigger_states/empro_states.py | 18 +++++++++--------- portal/trigger_states/models.py | 2 -- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/portal/trigger_states/empro_states.py b/portal/trigger_states/empro_states.py index dfabc783b..6bc6860a0 100644 --- a/portal/trigger_states/empro_states.py +++ b/portal/trigger_states/empro_states.py @@ -229,6 +229,7 @@ def evaluate_triggers(qnr): # transition and persist state sm.processed_triggers() ts.insert(from_copy=True) + current_app.logger.debug(f"QQQ{ts.user_id} post processed_triggers") current_app.logger.debug( "persist-trigger_states-new record state change to 'processed' " f"from evaluate_triggers() {ts}") @@ -260,27 +261,26 @@ def fire_trigger_events(): now = datetime.utcnow() def delay_processing(ts): - current_app.logger.debug("QQQ enter sequential_threshold_reached") + current_app.logger.debug(f"QQQ{ts.user_id} enter sequential_threshold_reached") """Give user time to respond to opt-out prompt if applicable""" if not ts.sequential_threshold_reached(): # not applicable unless at least one domain has adequate count - current_app.logger.debug("QQQ sequential_threshold_reached false, bail") + current_app.logger.debug(f"QQQ{ts.user_id} sequential_threshold_reached false, bail") return if ts.opted_out_domains(): # user must have already replied, if opted out of at least one - current_app.logger.debug("QQQ user already opted out") + current_app.logger.debug(f"QQQ{ts.user_id} user already opted out") return # check time since row transitioned to current state. delay # till threshold reached - current_app.logger.debug(f"QQQ row timestamp: {ts.timestamp} now: {datetime.utcnow()}") - current_app.logger.debug(f"QQQ row + delay {ts.timestamp + timedelta(seconds=OPT_OUT_DELAY)}") - current_app.logger.debug(f"QQQ tzinfo: {ts.timestamp.tzinfo} tz2: {(ts.timestamp + timedelta(seconds=OPT_OUT_DELAY)).tzinfo} tz3: {datetime.utcnow().tzinfo}") + current_app.logger.debug(f"QQQ{ts.user_id} row timestamp: {ts.timestamp} now: {datetime.utcnow()}") assert((ts.timestamp + timedelta(seconds=OPT_OUT_DELAY)).tzinfo == datetime.utcnow().tzinfo) if ts.timestamp + timedelta(seconds=OPT_OUT_DELAY) < datetime.utcnow(): - current_app.logger.debug(f"QQQ return True from delay_processing") + current_app.logger.debug(f"QQQ{ts.user_id} return True from delay_processing") return True + current_app.logger.debug(f"QQQ{ts.user_id} return None from delay_processing") def send_n_report(em, context, record): """Send email, append success/fail w/ context to record""" @@ -409,10 +409,10 @@ def process_pending_actions(ts): # seek out any pending "processed" work, i.e. triggers recently # evaluated for ts in TriggerState.query.filter(TriggerState.state == 'processed'): - current_app.logger.debug("QQQ call delay_processing") if delay_processing(ts): + current_app.logger.debug(f"QQQ{ts.user_id} delayed!!") continue - current_app.logger.debug("QQQ delay_processing didn't work") + current_app.logger.debug(f"QQQ{ts.user_id} NOT delayed!!") try: with TimeoutLock( key=EMPRO_LOCK_KEY.format(user_id=ts.user_id), diff --git a/portal/trigger_states/models.py b/portal/trigger_states/models.py index 70018ad8a..c1bfecece 100644 --- a/portal/trigger_states/models.py +++ b/portal/trigger_states/models.py @@ -190,13 +190,11 @@ def sequential_threshold_reached(self): """ from .empro_domains import sequential_hard_trigger_count_key if not self.triggers: - current_app.logger.debug("QQQ no triggers in sequential_threshold_reached") return for domain, link_triggers in self.triggers['domain'].items(): if link_triggers.get(sequential_hard_trigger_count_key, 0) > 2: return True - current_app.logger.debug(f"QQQ sequential not found in {link_triggers} sequential_threshold_reached") def reminder_due(self, as_of_date=None): """Determine if reminder is due from internal state""" From 1055405e8432b7bf161ff814697735b4ea673f58 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Mon, 22 Apr 2024 13:47:23 -0700 Subject: [PATCH 121/143] debugging log stmts only --- portal/trigger_states/empro_states.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/portal/trigger_states/empro_states.py b/portal/trigger_states/empro_states.py index 6bc6860a0..db03a0e47 100644 --- a/portal/trigger_states/empro_states.py +++ b/portal/trigger_states/empro_states.py @@ -275,9 +275,12 @@ def delay_processing(ts): # check time since row transitioned to current state. delay # till threshold reached - current_app.logger.debug(f"QQQ{ts.user_id} row timestamp: {ts.timestamp} now: {datetime.utcnow()}") - assert((ts.timestamp + timedelta(seconds=OPT_OUT_DELAY)).tzinfo == datetime.utcnow().tzinfo) - if ts.timestamp + timedelta(seconds=OPT_OUT_DELAY) < datetime.utcnow(): + now = datetime.utcnow() + current_app.logger.debug(f"QQQ{ts.user_id} row timestamp: {ts.timestamp} now: {now}") + filed_n_delay = ts.timestamp + timedelta(seconds=OPT_OUT_DELAY) + current_app.logger.debug(f"QQQ{ts.user_id} {filed_n_delay} < {now} : {filed_n_delay < now}") + current_app.logger.debug(f"QQQ{ts.user_id} {filed_n_delay.tzinfo}") + if filed_n_delay < now: current_app.logger.debug(f"QQQ{ts.user_id} return True from delay_processing") return True current_app.logger.debug(f"QQQ{ts.user_id} return None from delay_processing") From 3b23d466aa15f97e4091ddb4549ea8585553c3f5 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Mon, 22 Apr 2024 14:46:12 -0700 Subject: [PATCH 122/143] correct logic on delay --- portal/trigger_states/empro_states.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/portal/trigger_states/empro_states.py b/portal/trigger_states/empro_states.py index db03a0e47..aa2748ea2 100644 --- a/portal/trigger_states/empro_states.py +++ b/portal/trigger_states/empro_states.py @@ -278,9 +278,9 @@ def delay_processing(ts): now = datetime.utcnow() current_app.logger.debug(f"QQQ{ts.user_id} row timestamp: {ts.timestamp} now: {now}") filed_n_delay = ts.timestamp + timedelta(seconds=OPT_OUT_DELAY) - current_app.logger.debug(f"QQQ{ts.user_id} {filed_n_delay} < {now} : {filed_n_delay < now}") + current_app.logger.debug(f"QQQ{ts.user_id} {filed_n_delay} > {now} : {filed_n_delay < now}") current_app.logger.debug(f"QQQ{ts.user_id} {filed_n_delay.tzinfo}") - if filed_n_delay < now: + if filed_n_delay > now: current_app.logger.debug(f"QQQ{ts.user_id} return True from delay_processing") return True current_app.logger.debug(f"QQQ{ts.user_id} return None from delay_processing") From 3b0591e6ca6bcfd74fc69800453fc6fb503e681d Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Mon, 22 Apr 2024 16:22:51 -0700 Subject: [PATCH 123/143] add check for no action state --- portal/static/js/src/profile.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/portal/static/js/src/profile.js b/portal/static/js/src/profile.js index f21c4fd48..6c39a12af 100644 --- a/portal/static/js/src/profile.js +++ b/portal/static/js/src/profile.js @@ -1578,7 +1578,12 @@ export default (function() { const paramActionState = getUrlParameter("trigger_action_state"); // for debugging if (paramActionState) return paramActionState.toLowerCase(); - return String(this.subStudyTriggers.data.action_state).toLowerCase(); + const actionState = this.subStudyTriggers.data.action_state; + if (!actionState && + !this.shouldDisableSubstudyPostTx() && + this.hasSubStudyTriggers() + ) return "required"; + return String(actionState).toLowerCase(); }, hasMissedPostTxAction: function() { return this.hasSubStudyTriggers() && this.getPostTxActionStatus() === "missed"; From cd49c19d0a8f4ac3fb1ef68cf64695f89e365626 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Mon, 22 Apr 2024 16:58:59 -0700 Subject: [PATCH 124/143] clean up excessive logging --- portal/trigger_states/empro_states.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/portal/trigger_states/empro_states.py b/portal/trigger_states/empro_states.py index aa2748ea2..849983869 100644 --- a/portal/trigger_states/empro_states.py +++ b/portal/trigger_states/empro_states.py @@ -229,7 +229,6 @@ def evaluate_triggers(qnr): # transition and persist state sm.processed_triggers() ts.insert(from_copy=True) - current_app.logger.debug(f"QQQ{ts.user_id} post processed_triggers") current_app.logger.debug( "persist-trigger_states-new record state change to 'processed' " f"from evaluate_triggers() {ts}") @@ -261,29 +260,20 @@ def fire_trigger_events(): now = datetime.utcnow() def delay_processing(ts): - current_app.logger.debug(f"QQQ{ts.user_id} enter sequential_threshold_reached") """Give user time to respond to opt-out prompt if applicable""" if not ts.sequential_threshold_reached(): # not applicable unless at least one domain has adequate count - current_app.logger.debug(f"QQQ{ts.user_id} sequential_threshold_reached false, bail") return if ts.opted_out_domains(): # user must have already replied, if opted out of at least one - current_app.logger.debug(f"QQQ{ts.user_id} user already opted out") return # check time since row transitioned to current state. delay # till threshold reached - now = datetime.utcnow() - current_app.logger.debug(f"QQQ{ts.user_id} row timestamp: {ts.timestamp} now: {now}") filed_n_delay = ts.timestamp + timedelta(seconds=OPT_OUT_DELAY) - current_app.logger.debug(f"QQQ{ts.user_id} {filed_n_delay} > {now} : {filed_n_delay < now}") - current_app.logger.debug(f"QQQ{ts.user_id} {filed_n_delay.tzinfo}") - if filed_n_delay > now: - current_app.logger.debug(f"QQQ{ts.user_id} return True from delay_processing") + if filed_n_delay > datetime.utcnow(): return True - current_app.logger.debug(f"QQQ{ts.user_id} return None from delay_processing") def send_n_report(em, context, record): """Send email, append success/fail w/ context to record""" @@ -413,9 +403,7 @@ def process_pending_actions(ts): # evaluated for ts in TriggerState.query.filter(TriggerState.state == 'processed'): if delay_processing(ts): - current_app.logger.debug(f"QQQ{ts.user_id} delayed!!") continue - current_app.logger.debug(f"QQQ{ts.user_id} NOT delayed!!") try: with TimeoutLock( key=EMPRO_LOCK_KEY.format(user_id=ts.user_id), From 86d694235e63ef4ad75a4f4e629cf10608684894 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Tue, 23 Apr 2024 11:19:37 -0700 Subject: [PATCH 125/143] correct comment with migration reordering (previous commit only caught `down_revision` variable). --- portal/migrations/versions/d1f3ed8d16ef_.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/portal/migrations/versions/d1f3ed8d16ef_.py b/portal/migrations/versions/d1f3ed8d16ef_.py index 531bc8604..69d49e0d3 100644 --- a/portal/migrations/versions/d1f3ed8d16ef_.py +++ b/portal/migrations/versions/d1f3ed8d16ef_.py @@ -1,7 +1,7 @@ """remove Zulu locale Revision ID: d1f3ed8d16ef -Revises: 80c3b1e96c45 +Revises: 2e9b9e696bb8 Create Date: 2023-12-05 14:09:10.442328 """ From e9b58110aecea9835325dc157b945333a1e7b532 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Tue, 30 Apr 2024 17:50:20 -0700 Subject: [PATCH 126/143] no smoke, adding logging and tests looking for why the test system tried to send a "reminder" to an all opted-out patient's clinician. --- portal/trigger_states/empro_messages.py | 6 +++ tests/fixtures/trigger_state.py | 70 +++++++++++++++++++++++++ tests/test_trigger_states.py | 10 ++++ 3 files changed, 86 insertions(+) diff --git a/portal/trigger_states/empro_messages.py b/portal/trigger_states/empro_messages.py index c65223e23..b76c24388 100644 --- a/portal/trigger_states/empro_messages.py +++ b/portal/trigger_states/empro_messages.py @@ -109,6 +109,12 @@ def staff_emails(patient, hard_triggers, opted_out_domains, initial_notification # All triggered were opted out of - pick up different email template app_text_name += " all opted out" opt_in_domains = [] + if not initial_notification: + # seen on test, no idea how - include details in exception + msg = (f"Patient {patient.id} all opted out: {opted_out_domains} " + f"shouldn't be eligible for a reminder!") + current_app.logger.error(msg) + app_text_name = 'empro clinician trigger notification all opted out' elif opted_out_domains: app_text_name += " partially opted out" opt_in_domains = list(set(hard_triggers) - set(opted_out_domains)) diff --git a/tests/fixtures/trigger_state.py b/tests/fixtures/trigger_state.py index dcf82141e..ce4aa3083 100644 --- a/tests/fixtures/trigger_state.py +++ b/tests/fixtures/trigger_state.py @@ -22,6 +22,76 @@ def mock_triggers(): } +@fixture +def mock_opted_out_triggers(): + # pulled from test db, user 4231, id 1063 as of 4/30/24 + return { + "domain": { + "sad": { + "ironman_ss.17": "hard", + "ironman_ss.18": "hard", + "ironman_ss.19": "hard", + "_total_opted_out": 1, + "_opt_out_this_visit": True, + "_sequential_hard_trigger_count": 3}, + "anxious": { + "ironman_ss.11": "hard", + "ironman_ss.12": "hard", + "ironman_ss.13": "hard", + "_sequential_hard_trigger_count": 3}, + "fatigue": { + "ironman_ss.9": "hard", + "ironman_ss.10": "hard", + "_sequential_hard_trigger_count": 3}, + "insomnia": { + "ironman_ss.7": "hard", + "ironman_ss.8": "hard", + "_sequential_hard_trigger_count": 1}, + "joint_pain": { + "ironman_ss.4": "hard", + "ironman_ss.5": "hard", + "ironman_ss.6": "hard", + "_sequential_hard_trigger_count": 3}, + "discouraged": { + "ironman_ss.14": "hard", + "ironman_ss.15": "hard", + "ironman_ss.16": "hard", + "_sequential_hard_trigger_count": 3}, + "general_pain": { + "ironman_ss.1": "soft", + "ironman_ss.2": "soft", + "ironman_ss.3": "soft", + "_sequential_hard_trigger_count": 0}, + "social_isolation": { + "ironman_ss.20": "hard", + "_total_opted_out": 1, + "_opt_out_this_visit": True, + "_sequential_hard_trigger_count": 3} + }, + "source": { + "qb_id": 115, + "qnr_id": 4841, + "authored": "2024-04-25T19:24:57Z", + "qb_iteration": 1}, + "actions": { + "email": [ + {"context": "patient thank you", "timestamp": "2024-04-25T19:25:32.151704Z", "email_message_id": 214811}, + {"context": "initial staff alert", "timestamp": "2024-04-25T19:25:32.481573Z", "email_message_id": 214812}, + {"context": "initial staff alert", "timestamp": "2024-04-25T19:25:32.739550Z", "email_message_id": 214813}, + {"context": "initial staff alert", "timestamp": "2024-04-25T19:25:32.939555Z", "email_message_id": 214814}, + {"context": "initial staff alert", "timestamp": "2024-04-25T19:25:33.155603Z", "email_message_id": 214815}, + {"context": "initial staff alert", "timestamp": "2024-04-25T19:25:33.391523Z", "email_message_id": 214816}, + {"context": "initial staff alert", "timestamp": "2024-04-25T19:25:33.628547Z", "email_message_id": 214817}, + {"context": "initial staff alert", "timestamp": "2024-04-25T19:25:33.846962Z", "email_message_id": 214818}, + {"context": "initial staff alert", "timestamp": "2024-04-25T19:25:34.069484Z", "email_message_id": 214819}] + }, + "resolution": { + "qnr_id": 4842, + "authored": "2024-04-25T19:26:51Z", + "qb_iteration": None}, + "action_state": "completed" + } + @fixture def processed_ts(initialized_patient, mock_triggers): user_id = db.session.merge(initialized_patient).id diff --git a/tests/test_trigger_states.py b/tests/test_trigger_states.py index 9356b39ad..2e8a48f99 100644 --- a/tests/test_trigger_states.py +++ b/tests/test_trigger_states.py @@ -319,3 +319,13 @@ def test_subsequent_reminder_skips_weekends(initialized_patient): # until Monday assert ts.reminder_due(as_of_date=datetime.strptime( "2021-02-15T12:00:00Z", "%Y-%m-%dT%H:%M:%SZ")) + + +def test_counts_from_db_triggers(initialized_patient, mock_opted_out_triggers): + user_id = db.session.merge(initialized_patient).id + ts = TriggerState(user_id=user_id, triggers=mock_opted_out_triggers) + opted_out_domains = ts.opted_out_domains() + hard_triggers = ts.hard_trigger_list() + assert set(["sad", "social_isolation"]) == set(opted_out_domains) + assert 7 == len(hard_triggers) + assert (set(hard_triggers) - set(opted_out_domains)) From 774d88dc9f125f76bab34abda24e0bada13fffe6 Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Tue, 30 Apr 2024 18:16:30 -0700 Subject: [PATCH 127/143] as users may enter on email links to file EMPRO, allow such auth for all trigger APIs. --- portal/trigger_states/views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/portal/trigger_states/views.py b/portal/trigger_states/views.py index 845064642..b0366a9ac 100644 --- a/portal/trigger_states/views.py +++ b/portal/trigger_states/views.py @@ -55,7 +55,7 @@ def user_triggers(user_id): """ # confirm view access - get_user(user_id, 'view', allow_on_url_authenticated_encounters=True) + get_user(user_id, permission='view', allow_on_url_authenticated_encounters=True) return jsonify(users_trigger_state(user_id).as_json()) @@ -71,7 +71,7 @@ def opt_out(user_id): :returns: TriggerState in JSON for the requested visit month """ - get_user(user_id, 'edit', allow_on_url_authenticated_encounters=True) + get_user(user_id, permission='edit', allow_on_url_authenticated_encounters=True) ts = users_trigger_state(user_id) try: ts = ts.apply_opt_out(request.json) @@ -114,7 +114,7 @@ def user_trigger_history(user_id): """ # confirm view access - get_user(user_id, 'view') + get_user(user_id, permission='view', allow_on_url_authenticated_encounters=True) history = TriggerState.query.filter( TriggerState.user_id == user_id).order_by(TriggerState.id) From 220b9914e7e914310ef770b63b2f8cbe923715fe Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Wed, 1 May 2024 15:00:56 -0700 Subject: [PATCH 128/143] another guard clause, still puzzling over attempt to send reminder email to all opted out case. --- portal/trigger_states/empro_states.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/portal/trigger_states/empro_states.py b/portal/trigger_states/empro_states.py index 849983869..33dce182b 100644 --- a/portal/trigger_states/empro_states.py +++ b/portal/trigger_states/empro_states.py @@ -359,6 +359,9 @@ def process_pending_actions(ts): f"Invalid action_state {ts.triggers['action_state']} " f"for patient {ts.user_id}") + if not ts.hard_trigger_list(): + raise ValueError(f"{ts.user_id} should not require action without any hard triggers") + patient = User.query.get(ts.user_id) # Withdrawn users should never receive reminders, nor staff @@ -375,7 +378,10 @@ def process_pending_actions(ts): if ts.reminder_due(): pending_emails = staff_emails( - patient, ts.hard_trigger_list(), ts.opted_out_domains(), False) + patient=patient, + hard_triggers=ts.hard_trigger_list(), + opted_out_domains=ts.opted_out_domains(), + initial_notification=False) # necessary to make deep copy in order to update DB JSON triggers = copy.deepcopy(ts.triggers) From 6ed1f1c2c881b66aa007a432601f2c0cf6698e47 Mon Sep 17 00:00:00 2001 From: Ivan Cvitkovic Date: Wed, 1 May 2024 16:30:41 -0700 Subject: [PATCH 129/143] Pin NodeJS version (#4378) Pin NodeJS version used to build frontend --- bin/nodejs-wrapper.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bin/nodejs-wrapper.sh b/bin/nodejs-wrapper.sh index 74d04cf74..4ecf77b64 100755 --- a/bin/nodejs-wrapper.sh +++ b/bin/nodejs-wrapper.sh @@ -55,7 +55,8 @@ setup_node_venv() { python3 -m pip install nodeenv echo "Creating new virtual environment for NodeJS: ${node_venv_path}" - nodeenv "${node_venv_path}" + # TODO fix issue with vinyl-fs and NodeJS 22.0 + nodeenv --node=21.7.3 "${node_venv_path}" deactivate } From 7dc96e814cfd50757116dee3182c76b03afd2dba Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Thu, 2 May 2024 12:54:04 -0700 Subject: [PATCH 130/143] triggers API call check fix --- portal/static/js/src/empro.js | 29 ++++++++++++++++-------- portal/static/js/src/modules/TnthAjax.js | 2 +- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/portal/static/js/src/empro.js b/portal/static/js/src/empro.js index 4f327cb24..b436f1b9d 100644 --- a/portal/static/js/src/empro.js +++ b/portal/static/js/src/empro.js @@ -1,5 +1,6 @@ import EMPRO_DOMAIN_MAPPINGS from "./data/common/empro_domain_mappings.json"; import { + EPROMS_MAIN_STUDY_ID, EPROMS_SUBSTUDY_ID, EPROMS_SUBSTUDY_QUESTIONNAIRE_IDENTIFIER, EMPRO_TRIGGER_STATE_OPTOUT_KEY, @@ -220,7 +221,7 @@ emproObj.prototype.handleSubmitOptoutData = function () { { data: JSON.stringify(EmproObj.optOutSubmitData), max_attempts: 3, - timeout: 10000 // 10 seconds + timeout: 10000, // 10 seconds }, (data) => { return EmproObj.onAfterSubmitOptoutData(data); @@ -372,14 +373,24 @@ emproObj.prototype.init = function () { return; } tnthAjax.getUserResearchStudies(this.userId, "patient", false, (data) => { + if ( + !isDebugging && + data[EPROMS_MAIN_STUDY_ID] && + data[EPROMS_MAIN_STUDY_ID].ready + ) { + //don't present popup if main study is due + this.setLoadingVis(); + return false; + } if ( !isDebugging && (!data[EPROMS_SUBSTUDY_ID] || (data[EPROMS_SUBSTUDY_ID] && - data[EPROMS_SUBSTUDY_ID].errors && - data[EPROMS_SUBSTUDY_ID].errors.length)) + (data[EPROMS_SUBSTUDY_ID].ready || + (data[EPROMS_SUBSTUDY_ID].errors && + data[EPROMS_SUBSTUDY_ID].errors.length)))) ) { - //don't present popup if errors e.g. base study questionnaire due + //don't present popup if EMPRO study errors or EMPRO is due this.setLoadingVis(); return false; } @@ -396,7 +407,6 @@ emproObj.prototype.init = function () { if (!data) { data = TestResponsesJson; } - } console.log("Questionnaire response data: ", data); // no questionnaire data, just return here @@ -464,7 +474,7 @@ emproObj.prototype.init = function () { this.initTriggerDomains( { - maxTryAttempts: !autoShowModal ? 1 : 5, + maxTryAttempts: !autoShowModal ? 0 : 5, //no need to retry if thank you modal isn't supposed to show clearCache: autoShowModal, }, (result) => { @@ -559,14 +569,15 @@ emproObj.prototype.processTriggerData = function (data, historyData) { self.softTriggerDomains.push(key); } } - + if (self.optOutNotAllowed) { continue; } const MAX_ALLOWED_OPT_OUT_NUM = 3; // check if user has chosen to opt out this domain 3 times before const hasReachedMaxOptOut = processedHistoryData.find( - (item) => parseInt(item[key]["_total_opted_out"]) >= MAX_ALLOWED_OPT_OUT_NUM + (item) => + parseInt(item[key]["_total_opted_out"]) >= MAX_ALLOWED_OPT_OUT_NUM ); // if sequence count >= 3, the user can choose to opt_out of respective domain if ( @@ -629,7 +640,7 @@ emproObj.prototype.initTriggerDomains = function (params, callbackFunc) { currentTriggerData = TestTriggersJson; } if (!currentTriggerData) { - callback({ error: true, reason: "no trigger data"}); + callback({ error: true, reason: "no trigger data" }); return false; } this.processTriggerData(currentTriggerData, historyTriggerData); diff --git a/portal/static/js/src/modules/TnthAjax.js b/portal/static/js/src/modules/TnthAjax.js index 1d077ff3d..476edca03 100644 --- a/portal/static/js/src/modules/TnthAjax.js +++ b/portal/static/js/src/modules/TnthAjax.js @@ -341,7 +341,7 @@ export default { /*global $ */ callback = callback || function() {}; params = params || {}; params.retryAttempt = params.retryAttempt || 0; - params.maxTryAttempts = params.maxTryAttempts || MAX_ATTEMPTS; + params.maxTryAttempts = !isNaN(params.maxTryAttempts) ? params.maxTryAttempts : MAX_ATTEMPTS; if (!userId) { callback({error: i18next.t("User id is required.")}); From 1f82e32efd44f37f28fa8bc23c62df5429bde1cc Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Thu, 2 May 2024 14:02:00 -0700 Subject: [PATCH 131/143] add domain existence check, bug fix --- portal/static/js/src/empro.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/portal/static/js/src/empro.js b/portal/static/js/src/empro.js index b436f1b9d..67f9fb265 100644 --- a/portal/static/js/src/empro.js +++ b/portal/static/js/src/empro.js @@ -474,7 +474,7 @@ emproObj.prototype.init = function () { this.initTriggerDomains( { - maxTryAttempts: !autoShowModal ? 0 : 5, //no need to retry if thank you modal isn't supposed to show + maxTryAttempts: !autoShowModal ? 0 : isDebugging ? 0 : 5, //no need to retry if thank you modal isn't supposed to show clearCache: autoShowModal, }, (result) => { @@ -519,6 +519,10 @@ emproObj.prototype.processTriggerData = function (data, historyData) { console.log("No trigger data"); return false; } + + // set visit month related to trigger data + this.visitMonth = data.visit_month; + var self = this; let processedHistoryData = []; @@ -530,8 +534,9 @@ emproObj.prototype.processTriggerData = function (data, historyData) { console.log("processed history data ", processedHistoryData); - // set visit month related to trigger data - this.visitMonth = data.visit_month; + if (!data || !data.triggers || !data.triggers.domain) { + return; + } for (let key in data.triggers.domain) { if (!Object.keys(data.triggers.domain[key]).length) { @@ -577,6 +582,7 @@ emproObj.prototype.processTriggerData = function (data, historyData) { // check if user has chosen to opt out this domain 3 times before const hasReachedMaxOptOut = processedHistoryData.find( (item) => + item[key] && parseInt(item[key]["_total_opted_out"]) >= MAX_ALLOWED_OPT_OUT_NUM ); // if sequence count >= 3, the user can choose to opt_out of respective domain @@ -628,7 +634,7 @@ emproObj.prototype.initTriggerDomains = function (params, callbackFunc) { }) ), ]).then((results) => { - const currentTriggerData = + let currentTriggerData = results[0] && results[0].status === "fulfilled" && results[0].value ? results[0].value : null; @@ -636,7 +642,11 @@ emproObj.prototype.initTriggerDomains = function (params, callbackFunc) { results[1] && results[1].status === "fulfilled" && results[1].value ? results[1].value : null; - if (isDebugging && !currentTriggerData) { + if ( + isDebugging && + (!currentTriggerData || + (currentTriggerData && !currentTriggerData.triggers)) + ) { currentTriggerData = TestTriggersJson; } if (!currentTriggerData) { From aa905403da8c11d1af494215d7673f367b94edf5 Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Thu, 2 May 2024 14:56:13 -0700 Subject: [PATCH 132/143] label click event fix --- portal/static/js/src/empro.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/portal/static/js/src/empro.js b/portal/static/js/src/empro.js index 67f9fb265..2932c7ec1 100644 --- a/portal/static/js/src/empro.js +++ b/portal/static/js/src/empro.js @@ -284,9 +284,7 @@ emproObj.prototype.initOptOutElementEvents = function () { EmproObj.setOptoutError(""); var domain = $(this).attr("data-domain"); var associatedCkInput = $("#emproOptOutModal .ck-input-" + domain); - if (associatedCkInput.is(":checked")) - associatedCkInput.prop("checked", false); - else associatedCkInput.prop("checked", true); + associatedCkInput.trigger("click"); }); // continue button that displays when error From 4769b7a68937bd3ba4e737cc2ba763ab2c939ada Mon Sep 17 00:00:00 2001 From: Paul Bugni Date: Mon, 13 May 2024 16:19:15 -0700 Subject: [PATCH 133/143] cut&paste error on adherence report, opt_out change. --- portal/models/reporting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/portal/models/reporting.py b/portal/models/reporting.py index 65b493652..3274e5778 100644 --- a/portal/models/reporting.py +++ b/portal/models/reporting.py @@ -141,7 +141,7 @@ def empro_row_detail(row, ts_reporting): or "") ht = ts_reporting.hard_triggers_for_visit(visit_month) row['hard_trigger_domains'] = ', '.join(ht) if ht else "" - oo = ts_reporting.soft_triggers_for_visit(visit_month) + oo = ts_reporting.opted_out_domains_for_visit(visit_month) row['opted_out_domains'] = ', '.join(oo) if oo else "" st = ts_reporting.soft_triggers_for_visit(visit_month) row['soft_trigger_domains'] = ', '.join(st) if st else "" From 4861c170ecb5a019a889de025f10c5111596d20c Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Thu, 23 May 2024 15:24:06 -0700 Subject: [PATCH 134/143] longitudinal report opt out changes --- .../js/src/components/LongitudinalReport.vue | 41 ++++++++++--------- portal/static/js/src/modules/TnthAjax.js | 5 ++- portal/static/less/eproms.less | 6 +-- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/portal/static/js/src/components/LongitudinalReport.vue b/portal/static/js/src/components/LongitudinalReport.vue index 14e4b4c16..7ce578c22 100644 --- a/portal/static/js/src/components/LongitudinalReport.vue +++ b/portal/static/js/src/components/LongitudinalReport.vue @@ -10,13 +10,12 @@ - ⓘ (do not contact)
- < - > + < + >
@@ -54,6 +53,7 @@ EMPRO_TRIGGER_STATE_OPTOUT_KEY, EMPRO_TRIGGER_PROCCESSED_STATES } from "../data/common/consts.js"; + let resizeVisIntervalId = 0; export default { data () { return { @@ -113,9 +113,7 @@ /* * display column(s) responsively based on viewport width */ - if (bodyWidth >= 1400) { - this.maxToShow = 4; - } else if (bodyWidth >= 992) { + if (bodyWidth >= 992) { this.maxToShow = 3; } else if (bodyWidth >= 699) { this.maxToShow = 2; @@ -124,12 +122,15 @@ } return; }, + shouldHideNav() { + return this.questionnaireDates.length <= 1; + }, setNavIndexes() { /* * set initial indexes for start and end navigation buttons */ - this.navEndIndex = this.maxToShow >= this.questionnaireDates.length ? this.questionnaireDates.length: this.maxToShow; - this.navStartIndex = 1; + this.navEndIndex = this.questionnaireDates.length > 0 ? this.questionnaireDates.length : 1; + this.navStartIndex = this.navEndIndex - this.maxToShow + 1; }, setGoForward() { /* @@ -327,19 +328,17 @@ let optionsLength = this.getQuestionOptions(entry.linkId); let answerObj = { q: q, - a: a + (hardTriggers.length?" **": (softTriggers.length?" *": (optedOutTriggers.length? "  ":""))), + a: a + (hardTriggers.length?" **": ((optedOutTriggers.length || softTriggers.length)?" *": (optedOutTriggers.length? "  ":""))), linkId: entry.linkId, value: answerValue, - cssClass: - optedOutTriggers.length ? - "warning" : - //last - ( - answerValue >= optionsLength.length ? "darkest" : + cssClass: ( + answerValue >= optionsLength.length ? + "darkest" : //penultimate - (answerValue >= optionsLength.length - 1 ? "darker": - (answerValue <= 1 ? "no-value": "")) - ) + answerValue >= optionsLength.length - 1 ? + "darker" : + (answerValue <= 1 ? "no-value": "") + ) }; this.data[index].data.push(answerObj); let currentDomain = ""; @@ -370,7 +369,11 @@ } $(window).on("resize", () => { window.requestAnimationFrame(() => { - this.setInitVis(); + if (this.shouldHideNav()) return; + clearTimeout(resizeVisIntervalId); + resizeVisIntervalId = setTimeout(() => { + this.setInitVis(); + }, 250); }); }); }, diff --git a/portal/static/js/src/modules/TnthAjax.js b/portal/static/js/src/modules/TnthAjax.js index 476edca03..34b04dbf2 100644 --- a/portal/static/js/src/modules/TnthAjax.js +++ b/portal/static/js/src/modules/TnthAjax.js @@ -98,6 +98,7 @@ export default { /*global $ */ ).fail(function() { if (callback) { callback({"error": DEFAULT_SERVER_DATA_ERROR}); + fieldHelper.showError(targetField); } }); return; @@ -159,7 +160,7 @@ export default { /*global $ */ loadingField.animate({"opacity": 0}, __timeout, function() { successField.animate({"opacity": 1}, __timeout, function() { setTimeout(function() { - successField.animate({"opacity": 0}, __timeout * 2); + successField.animate({"opacity": 0}, __timeout * 4); }, __timeout * 2); }); }); @@ -182,7 +183,7 @@ export default { /*global $ */ loadingField.animate({"opacity": 0}, __timeout, function() { errorField.animate({"opacity": 1}, __timeout, function() { setTimeout(function() { - errorField.animate({"opacity": 0}, __timeout * 2); + errorField.animate({"opacity": 0}, __timeout * 4); }, __timeout * 2); }); }); diff --git a/portal/static/less/eproms.less b/portal/static/less/eproms.less index 091f9087d..250bb89df 100644 --- a/portal/static/less/eproms.less +++ b/portal/static/less/eproms.less @@ -3805,11 +3805,11 @@ section.header { } } .answer { - border-radius: 32px; + border-radius: 50vmax; background: @muterColor; display: inline-block; - padding: 8px 16px; - min-width: 132px; + padding: 8px 24px; + min-width: 180px; text-align: center; &.warning { background-color: darken(@warningColor, 1%); From 95cdb5380b968cf72519150866ae014a718fe3db Mon Sep 17 00:00:00 2001 From: Amy Chen Date: Wed, 12 Jun 2024 14:50:33 -0700 Subject: [PATCH 135/143] TN-3298 add delayed response option due to holiday checkbox to post tx questionnaire --- portal/config/eproms/Questionnaire.json | 13 ++++++++ portal/static/js/src/profile.js | 6 +++- portal/static/less/eproms.less | 31 ++++++++++++++++++++ portal/templates/profile/profile_macros.html | 25 ++++++++++------ 4 files changed, 65 insertions(+), 10 deletions(-) diff --git a/portal/config/eproms/Questionnaire.json b/portal/config/eproms/Questionnaire.json index 2ace521d2..6548748e1 100644 --- a/portal/config/eproms/Questionnaire.json +++ b/portal/config/eproms/Questionnaire.json @@ -8332,6 +8332,19 @@ "linkId": "ironman_ss_post_tx.2", "text": "Date action taken" }, + { + "type": "open-choice", + "linkId": "ironman_ss_post_tx.2.1", + "option": [ + { + "valueCoding": { + "code": "ironman_ss_post_tx.2.1", + "display": "Delayed due to local public holiday" + } + } + ], + "required": false + }, { "type": "choice", "linkId": "ironman_ss_post_tx.3", diff --git a/portal/static/js/src/profile.js b/portal/static/js/src/profile.js index 6c39a12af..7f0dcb39a 100644 --- a/portal/static/js/src/profile.js +++ b/portal/static/js/src/profile.js @@ -1607,6 +1607,10 @@ export default (function() { if (this.getPostTxActionStatus() === EMPRO_TRIGGER_WITHDRAWN_STATE) return true; // subject withdrawn return this.getPostTxActionStatus() === "completed" || this.subStudyTriggers.data.resolution; }, + getDataRequiredAttribute:function(element) { + if (!element) return null; + return element.hasOwnProperty("required") ? String(element.required) : "true"; + }, onResponseChangeFieldEvent: function(event) { let targetElement = $(event.target); let containerIdentifier = "#postTxQuestionnaireContainer"; @@ -1635,7 +1639,7 @@ export default (function() { } let answeredNum = 0; $(`${containerIdentifier} .question`).each(function() { - if ($(this).find("[answered]").length) { + if ($(this).find("[answered]").length || $(this).find("[dataRequired='false']").length) { answeredNum++; } }); diff --git a/portal/static/less/eproms.less b/portal/static/less/eproms.less index 250bb89df..f6791eeb3 100644 --- a/portal/static/less/eproms.less +++ b/portal/static/less/eproms.less @@ -3993,6 +3993,12 @@ section.header { } .question { margin-bottom: 24px; + display: flex; + align-items: flex-start; + &:has(.title:empty) { + margin-top: -24px; + margin-bottom: 0; + } .title { display: inline-block; margin-right: 8px; @@ -4003,6 +4009,11 @@ section.header { color: @baseColor; } } + .flex { + input { + margin: 0; + } + } } .choices { margin-top: 8px; @@ -4016,6 +4027,11 @@ section.header { } } } + .question:first-of-type { + .choices { + margin-top: 0; + } + } select, .data-datepicker { display: inline-block; @@ -5489,6 +5505,21 @@ body.vis-on-callback { /* allow page-specific presentation of content after load max-width: 100%; } } +.flex-align-center { + align-items: center; +} +.flex-row-gap-1x { + row-gap: 8px +} +.flex-column-gap-1x { + column-gap: 8px +} +.flex-column { + display: flex; + flex-direction: column; + align-items: flex-start; + row-gap: 8px; +} .flex-in-between { display: flex; justify-content: space-between; diff --git a/portal/templates/profile/profile_macros.html b/portal/templates/profile/profile_macros.html index 73c51c47b..3f78351e8 100644 --- a/portal/templates/profile/profile_macros.html +++ b/portal/templates/profile/profile_macros.html @@ -559,7 +559,7 @@

{{ _("Communica
- {{saveLoaderDiv("clinics")}} + {{saveLoaderDiv("clinics")}} {% endcall %}
@@ -937,20 +937,24 @@

{{_("Actions Required (for C - +
+ +
+ :dataRequired="getDataRequiredAttribute(item)" + v-if="item.type == 'boolean'" />
@@ -962,6 +966,7 @@
{{_("Actions Required (for C :dataType="item.type" :linkId="item.linkId" :value="option.valueCoding.display" + :dataRequired="getDataRequiredAttribute(item)" @change="onResponseChangeFieldEvent" > @@ -971,6 +976,7 @@
{{_("Actions Required (for C dataType="string" :code="option.valueCoding.code" :linkId="item.linkId" + :dataRequired="getDataRequiredAttribute(item)" @change="onResponseChangeFieldEvent" v-if="/other/gi.test(option.valueCoding.display)" > @@ -980,6 +986,7 @@
{{_("Actions Required (for C -
+ + v-if="item.type == 'boolean'">
@@ -966,7 +962,6 @@
{{_("Actions Required (for C :dataType="item.type" :linkId="item.linkId" :value="option.valueCoding.display" - :dataRequired="getDataRequiredAttribute(item)" @change="onResponseChangeFieldEvent" > @@ -976,7 +971,6 @@
{{_("Actions Required (for C dataType="string" :code="option.valueCoding.code" :linkId="item.linkId" - :dataRequired="getDataRequiredAttribute(item)" @change="onResponseChangeFieldEvent" v-if="/other/gi.test(option.valueCoding.display)" > @@ -986,7 +980,6 @@
{{_("Actions Required (for C + +
+ +
+ + - - + :dataRequired="getDataRequiredAttribute(item)" + v-if="item.type == 'boolean'" /> +
@@ -962,6 +970,7 @@
{{_("Actions Required (for C :dataType="item.type" :linkId="item.linkId" :value="option.valueCoding.display" + :dataRequired="getDataRequiredAttribute(item)" @change="onResponseChangeFieldEvent" > @@ -971,6 +980,7 @@
{{_("Actions Required (for C dataType="string" :code="option.valueCoding.code" :linkId="item.linkId" + :dataRequired="getDataRequiredAttribute(item)" @change="onResponseChangeFieldEvent" v-if="/other/gi.test(option.valueCoding.display)" > @@ -980,6 +990,7 @@
{{_("Actions Required (for C