Skip to content

Commit d9653bc

Browse files
Merge pull request #597 from biocore/csymons_cheek_samples
Cheek Samples
2 parents e0f74be + 9909247 commit d9653bc

File tree

12 files changed

+344
-18
lines changed

12 files changed

+344
-18
lines changed

microsetta_private_api/admin/admin_impl.py

+1
Original file line numberDiff line numberDiff line change
@@ -787,6 +787,7 @@ def list_barcode_query_fields(token_info):
787787
'input': 'select',
788788
'values': {
789789
"Blood (skin prick)": "Blood (skin prick)",
790+
"Cheek": "Cheek",
790791
"Saliva": "Saliva",
791792
"Ear wax": "Ear wax",
792793
"Forehead": "Forehead",

microsetta_private_api/api/_sample.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,15 @@ def update_sample_association(account_id, source_id, sample_id, body,
148148
# sample_site will not be present if its environmental. this will
149149
# default to None if the key is not present
150150
sample_site = body.get('sample_site')
151+
152+
barcode_meta = body.get('barcode_meta')
153+
151154
sample_info = SampleInfo(
152155
sample_id,
153156
sample_datetime,
154157
sample_site,
155-
body["sample_notes"]
158+
body["sample_notes"],
159+
barcode_meta
156160
)
157161

158162
sample_repo.update_info(account_id, source_id, sample_info,

microsetta_private_api/api/microsetta_private_api.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -3589,7 +3589,7 @@ components:
35893589
nullable: true
35903590
sample_site:
35913591
enum: ["Blood (skin prick)", "Saliva", "Ear wax", "Forehead", "Fur", "Hair", "Left hand", "Left leg", "Mouth", "Nares", "Nasal mucus",
3592-
"Right hand", "Right leg", "Stool", "Tears", "Torso", "Vaginal mucus", null]
3592+
"Right hand", "Right leg", "Stool", "Tears", "Torso", "Vaginal mucus", "Cheek", null]
35933593
example: "Stool"
35943594
sample_edit_locked:
35953595
type: boolean

microsetta_private_api/api/tests/test_api.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@
182182
'account_id': None,
183183
'source_id': None,
184184
'sample_site': None,
185+
'barcode_meta': {},
185186
'sample_project_ids': [1]}
186187

187188
DUMMY_FILLED_SAMPLE_INFO = {
@@ -583,7 +584,8 @@ def create_dummy_sample_objects(filled=False):
583584
info_dict["sample_id"],
584585
datetime_obj,
585586
info_dict["sample_site"],
586-
info_dict["sample_notes"]
587+
info_dict["sample_notes"],
588+
{}
587589
)
588590

589591
sample = Sample(info_dict["sample_id"],

microsetta_private_api/api/tests/test_integration.py

+1
Original file line numberDiff line numberDiff line change
@@ -1443,6 +1443,7 @@ def _test_edit_sample_info(self, source_type):
14431443
if store_sample_site:
14441444
fuzzy_info['sample_site'] = "Tears"
14451445
fuzzy_info['sample_datetime'] = datetime.datetime.utcnow()
1446+
fuzzy_info['barcode_meta'] = {}
14461447

14471448
# Many fields are not writable, each should individually cause failure.
14481449
readonly_fields = [
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
-- Beginning with cheek samples, we're collecting metadata that are explicitly
2+
-- linked to sample collection (unlike surveys, which are implicitly linked
3+
-- to samples via sources), but not globally collected, and therefore don't
4+
-- belong in the ag.ag_kit_barcodes table. A new table will store these
5+
-- fields and could eventually be extended to a much more robust framework.
6+
7+
-- First, we need to set up an ENUM type to enforce values for the type of
8+
-- product used to last wash their face
9+
CREATE TYPE SAMPLE_SITE_LAST_WASHED_PRODUCT_TYPE AS ENUM ('Soap (includes bar and liquid soap)', 'Foaming face wash', 'Face cleanser', 'Just water', 'Other (e.g. shampoo, body wash, all-in-one or all-over wash)', 'Not sure');
10+
11+
-- Then, create the table to store the data
12+
-- Note: the date and time are stored separately because we're not enforcing
13+
-- either as a required field. As such, using a timestamp type would not be
14+
-- appropriate since it forces us into a both or neither paradigm.
15+
CREATE TABLE ag.ag_kit_barcodes_cheek (
16+
ag_kit_barcode_id UUID NOT NULL PRIMARY KEY,
17+
sample_site_last_washed_date DATE,
18+
sample_site_last_washed_time TIME,
19+
sample_site_last_washed_product SAMPLE_SITE_LAST_WASHED_PRODUCT_TYPE,
20+
21+
-- Foreign key relationship on ag_kit_barcode_id
22+
CONSTRAINT fk_ag_kit_barcode_id FOREIGN KEY (ag_kit_barcode_id) REFERENCES ag.ag_kit_barcodes (ag_kit_barcode_id)
23+
);

microsetta_private_api/model/sample.py

+13-2
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,17 @@ def __init__(self, sample_id, datetime_collected, site, notes, barcode,
2626
self.account_id = account_id
2727

2828
self.accession_urls = []
29+
self.barcode_meta = {}
2930
self.kit_id = kit_id
3031

3132
self._sample_project_ids = sample_project_ids
3233

3334
def set_accession_urls(self, accession_urls):
3435
self.accession_urls = accession_urls
3536

37+
def set_barcode_meta(self, barcode_meta):
38+
self.barcode_meta = barcode_meta
39+
3640
def get_project_ids(self):
3741
return self._sample_project_ids
3842

@@ -81,18 +85,25 @@ def to_api(self):
8185
"account_id": self.account_id,
8286
"sample_projects": list(self.sample_projects),
8387
"accession_urls": self.accession_urls,
84-
"kit_id": self.kit_id
88+
"kit_id": self.kit_id,
89+
"barcode_meta": self.barcode_meta
8590
}
8691

8792

8893
# A SampleInfo represents the set of end user editable fields whose lifetime
8994
# matches that of the association between a sample and a source
9095
class SampleInfo:
91-
def __init__(self, sample_id, datetime_collected, site, notes):
96+
def __init__(self, sample_id, datetime_collected, site, notes,
97+
barcode_meta=None):
9298
self.id = sample_id
9399
# NB: datetime_collected may be None if sample not yet used
94100
self.datetime_collected = datetime_collected
95101
# NB: notes may be None
96102
self.notes = notes
97103
# NB: site may be None if sample not yet used
98104
self.site = site
105+
106+
if barcode_meta is None:
107+
self.barcode_meta = {}
108+
else:
109+
self.barcode_meta = barcode_meta

microsetta_private_api/repo/metadata_repo/_constants.py

+13-2
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@
237237
'host_body_habitat': 'UBERON:feces',
238238
'env_material': 'feces',
239239
'env_package': 'human-gut',
240-
'description': 'American Gut Project Stool sample',
240+
'description': 'Microsetta Initiative Stool sample',
241241
'host_body_site': 'UBERON:feces'},
242242
'Forehead': {
243243
'host_body_product': 'UBERON:sebum',
@@ -356,7 +356,18 @@
356356
'env_package': 'human-associated',
357357
'description': 'American Gut Project Ear wax sample',
358358
'empo_3': 'Animal secretion',
359-
'host_body_site': 'UBERON:external auditory meatus'}
359+
'host_body_site': 'UBERON:external auditory meatus'},
360+
'Cheek': {
361+
'host_body_product': 'UBERON:sebum',
362+
'sample_type': 'skin of cheek',
363+
'scientific_name': 'human skin metagenome',
364+
'taxon_id': '539655',
365+
'host_body_habitat': 'UBERON:skin',
366+
'empo_3': 'Animal surface',
367+
'env_material': 'sebum material',
368+
'env_package': 'human-skin',
369+
'description': 'Microsetta Initiative cheek skin sample',
370+
'host_body_site': 'UBERON:skin of cheek'}
360371
}
361372

362373

microsetta_private_api/repo/metadata_repo/_repo.py

+4
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,10 @@ def _to_pandas_series(metadata, multiselect_map):
710710
index.append(shortname)
711711
values.append(answer)
712712

713+
for variable, value in sample_detail.barcode_meta.items():
714+
index.append(variable)
715+
values.append(value)
716+
713717
for variable, value in sample_invariants.items():
714718
index.append(variable)
715719
values.append(value)

microsetta_private_api/repo/metadata_repo/tests/test_repo.py

+15-8
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,10 @@ def setUp(self):
6767
"sample": MM({
6868
"sample_projects": ["American Gut Project"],
6969
"datetime_collected": "2013-10-15T09:30:00",
70-
"site": "Stool"
70+
"site": "Stool",
71+
"barcode_meta": {
72+
"sample_site_last_washed_date": "01/10/2025"
73+
}
7174
}),
7275
'survey_answers': [
7376
{'template': 1,
@@ -134,7 +137,8 @@ def setUp(self):
134137
"sample": MM({
135138
"sample_projects": ["American Gut Project"],
136139
"datetime_collected": "2013-10-15T09:30:00",
137-
"site": "Stool"
140+
"site": "Stool",
141+
"barcode_meta": {}
138142
}),
139143
'survey_answers': [
140144
{'template': 1,
@@ -170,7 +174,8 @@ def setUp(self):
170174
"sample": MM({
171175
"sample_projects": ["American Gut Project"],
172176
"datetime_collected": "2013-10-15T09:30:00",
173-
"site": "Stool"
177+
"site": "Stool",
178+
"barcode_meta": {}
174179
}),
175180
'survey_answers': [
176181
{'template': SurveyTemplateRepo.DIET_ID,
@@ -406,13 +411,13 @@ def test_to_pandas_dataframe(self):
406411
'true', 'true', 'false', 'false',
407412
UNSPECIFIED,
408413
'okay', 'No', "2013-10-15T09:30:00", '000004216',
409-
'US:CA', 'CA', '33', '-117'],
414+
'US:CA', 'CA', '33', '-117', '01/10/2025'],
410415
['XY0004216', 'bar', 'Vegan foo', 'Yes',
411416
UNSPECIFIED, UNSPECIFIED, UNSPECIFIED,
412417
'No', 'false', 'true', 'true', 'false',
413418
'foobar', UNSPECIFIED, UNSPECIFIED,
414419
"2013-10-15T09:30:00", 'XY0004216',
415-
'US:CA', 'CA', '33', '-117']],
420+
'US:CA', 'CA', '33', '-117', 'not provided']],
416421
columns=['sample_name', 'host_subject_id',
417422
'diet_type', 'multivitamin',
418423
'probiotic_frequency',
@@ -425,7 +430,8 @@ def test_to_pandas_dataframe(self):
425430
'sample2specific', 'abc', 'def',
426431
'collection_timestamp',
427432
'anonymized_name', 'geo_loc_name',
428-
'state', 'latitude', 'longitude']
433+
'state', 'latitude', 'longitude',
434+
'sample_site_last_washed_date']
429435
).set_index('sample_name')
430436

431437
for k, v in HUMAN_SITE_INVARIANTS['Stool'].items():
@@ -453,15 +459,16 @@ def test_to_pandas_series(self):
453459
values = ['foo', '', 'No', 'Unspecified', 'Unspecified',
454460
'Unspecified', 'No', 'true', 'true', 'false',
455461
'false', 'okay', 'No',
456-
'2013-10-15T09:30:00', 'US:CA', 'CA', '33', '-117']
462+
'2013-10-15T09:30:00', 'US:CA', 'CA', '33', '-117',
463+
'01/10/2025']
457464
index = ['HOST_SUBJECT_ID', 'DIET_TYPE', 'MULTIVITAMIN',
458465
'PROBIOTIC_FREQUENCY', 'VITAMIN_B_SUPPLEMENT_FREQUENCY',
459466
'VITAMIN_D_SUPPLEMENT_FREQUENCY',
460467
'OTHER_SUPPLEMENT_FREQUENCY',
461468
'ALLERGIC_TO_blahblah', 'ALLERGIC_TO_stuff', 'ALLERGIC_TO_x',
462469
'ALLERGIC_TO_baz', 'abc', 'def',
463470
'COLLECTION_TIMESTAMP', 'GEO_LOC_NAME', 'STATE', 'LATITUDE',
464-
'LONGITUDE']
471+
'LONGITUDE', 'sample_site_last_washed_date']
465472

466473
for k, v in HUMAN_SITE_INVARIANTS['Stool'].items():
467474
values.append(v)

0 commit comments

Comments
 (0)