diff --git a/data-serving/data-service/schemas/cases.schema.json b/data-serving/data-service/schemas/cases.schema.json index c5be96949..abcf4fa97 100644 --- a/data-serving/data-service/schemas/cases.schema.json +++ b/data-serving/data-service/schemas/cases.schema.json @@ -73,6 +73,37 @@ } } }, + "genomeSequences": { + "bsonType": "array", + "uniqueItems": true, + "items": { + "bsonType": "object", + "additionalProperties": false, + "properties": { + "_id": { + "bsonType": "objectId" + }, + "sampleCollectionDate": { + "bsonType": "date" + }, + "repositoryUrl": { + "bsonType": "string" + }, + "sequenceId": { + "bsonType": "string" + }, + "sequenceName": { + "bsonType": "string" + }, + "sequenceLength": { + "bsonType": "int" + }, + "notes": { + "bsonType": "string" + } + } + } + }, "location": { "bsonType": "object", "additionalProperties": false, diff --git a/data-serving/data-service/src/model/case.ts b/data-serving/data-service/src/model/case.ts index 896f171d3..d2a863b7f 100644 --- a/data-serving/data-service/src/model/case.ts +++ b/data-serving/data-service/src/model/case.ts @@ -2,6 +2,10 @@ import { CaseReferenceDocument, caseReferenceSchema } from './case-reference'; import { DemographicsDocument, demographicsSchema } from './demographics'; import { DictionaryDocument, dictionarySchema } from './dictionary'; import { EventDocument, eventSchema } from './event'; +import { + GenomeSequenceDocument, + genomeSequenceSchema, +} from './genome-sequence'; import { LocationDocument, locationSchema } from './location'; import { PathogenDocument, pathogenSchema } from './pathogen'; import { @@ -27,6 +31,7 @@ const caseSchema = new mongoose.Schema( message: 'Must include an event with name "confirmed"', }, }, + genomeSequences: [genomeSequenceSchema], importedCase: {}, location: locationSchema, revisionMetadata: { @@ -68,6 +73,7 @@ type CaseDocument = mongoose.Document & { caseReference: CaseReferenceDocument; demographics: DemographicsDocument; events: [EventDocument]; + genomeSequences: [GenomeSequenceDocument]; importedCase: {}; location: LocationDocument; revisionMetadata: RevisionMetadataDocument; diff --git a/data-serving/data-service/src/model/genome-sequence.ts b/data-serving/data-service/src/model/genome-sequence.ts new file mode 100644 index 000000000..e5daf4de8 --- /dev/null +++ b/data-serving/data-service/src/model/genome-sequence.ts @@ -0,0 +1,21 @@ +import { dateFieldInfo } from './date'; +import mongoose from 'mongoose'; +import { positiveIntFieldInfo } from './positive-int'; + +export const genomeSequenceSchema = new mongoose.Schema({ + sampleCollectionDate: dateFieldInfo, + repositoryUrl: String, + sequenceId: String, + sequenceName: String, + sequenceLength: positiveIntFieldInfo, + notes: String, +}); + +export type GenomeSequenceDocument = mongoose.Document & { + sampleCollectionDate: Date; + repositoryUrl: string; + sequenceId: string; + sequenceName: string; + sequenceLength: number; + notes: string; +}; diff --git a/data-serving/data-service/test/model/data/case.full.json b/data-serving/data-service/test/model/data/case.full.json index 0076f3ae1..9c65d59e8 100644 --- a/data-serving/data-service/test/model/data/case.full.json +++ b/data-serving/data-service/test/model/data/case.full.json @@ -13,6 +13,16 @@ "nationality": "Swedish", "ethnicity": "Other" }, + "genomeSequences": [ + { + "sampleCollectionDate": "2019-12-30", + "repositoryUrl": "https://www.ncbi.nlm.nih.gov/nuccore/NC_045512", + "sequenceId": "NC_045512.2", + "sequenceName": "Severe acute respiratory syndrome coronavirus 2 isolate Wuhan-Hu-1, complete genome", + "sequenceLength": 33000, + "notes": "The reference sequence is identical to MN908947." + } + ], "location": { "country": "France", "administrativeAreaLevel1": "Île-de-France", diff --git a/data-serving/data-service/test/model/data/genome-sequence.full.json b/data-serving/data-service/test/model/data/genome-sequence.full.json new file mode 100644 index 000000000..d9d7d1386 --- /dev/null +++ b/data-serving/data-service/test/model/data/genome-sequence.full.json @@ -0,0 +1,8 @@ +{ + "sampleCollectionDate": "2019-12-30", + "repositoryUrl": "https://www.ncbi.nlm.nih.gov/nuccore/NC_045512", + "sequenceId": "NC_045512.2", + "sequenceName": "Severe acute respiratory syndrome coronavirus 2 isolate Wuhan-Hu-1, complete genome", + "sequenceLength": 33000, + "notes": "The reference sequence is identical to MN908947." +} \ No newline at end of file diff --git a/data-serving/data-service/test/model/data/genome-sequence.minimal.json b/data-serving/data-service/test/model/data/genome-sequence.minimal.json new file mode 100644 index 000000000..9e26dfeeb --- /dev/null +++ b/data-serving/data-service/test/model/data/genome-sequence.minimal.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/data-serving/data-service/test/model/genome-sequence.test.ts b/data-serving/data-service/test/model/genome-sequence.test.ts new file mode 100644 index 000000000..7da2acf4e --- /dev/null +++ b/data-serving/data-service/test/model/genome-sequence.test.ts @@ -0,0 +1,42 @@ +import { + GenomeSequenceDocument, + genomeSequenceSchema, +} from '../../src/model/genome-sequence'; + +import { Error } from 'mongoose'; +import fullModel from './data/genome-sequence.full.json'; +import minimalModel from './data/genome-sequence.minimal.json'; +import mongoose from 'mongoose'; + +const GenomeSequence = mongoose.model( + 'GenomeSequence', + genomeSequenceSchema, +); + +describe('validate', () => { + it('genome sequence with non-conforming date is invalid', async () => { + return new GenomeSequence({ + ...minimalModel, + ...{ sampleCollectionDate: Date.parse('2019-10-31') }, + }).validate((e) => { + expect(e.name).toBe(Error.ValidationError.name); + }); + }); + + it('genome sequence with non-integer length is invalid', async () => { + return new GenomeSequence({ + ...minimalModel, + ...{ sequenceLength: 2.2 }, + }).validate((e) => { + expect(e.name).toBe(Error.ValidationError.name); + }); + }); + + it('minimal genome sequence model is valid', async () => { + return new GenomeSequence(minimalModel).validate(); + }); + + it('fully specified genome sequence model is valid', async () => { + return new GenomeSequence(fullModel).validate(); + }); +}); diff --git a/data-serving/samples/cases.json b/data-serving/samples/cases.json index 519e405f1..5f0d443bd 100644 --- a/data-serving/samples/cases.json +++ b/data-serving/samples/cases.json @@ -12,6 +12,18 @@ ], "ethnicity": "Other" }, + "genomeSequences": [ + { + "sampleCollectionDate": { + "$date": "2019-12-30T00:00:00Z" + }, + "repositoryUrl": "https://www.ncbi.nlm.nih.gov/nuccore/NC_045512", + "sequenceId": "NC_045512.2", + "sequenceName": "Severe acute respiratory syndrome coronavirus 2 isolate Wuhan-Hu-1, complete genome", + "sequenceLength": 33000, + "notes": "The reference sequence is identical to MN908947." + } + ], "location": { "country": "France", "administrativeAreaLevel1": "Île-de-France",