From b68036be054a77dbe7e40428de79a44fda220439 Mon Sep 17 00:00:00 2001 From: anna-parker <50943381+anna-parker@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:29:53 +0100 Subject: [PATCH 01/44] feat(prepro): refactor - more nextclade functions to their own file, make the process_single cleaner --- .../nextclade/src/loculus_preprocessing/__init__.py | 1 + .../nextclade/src/loculus_preprocessing/prepro.py | 12 ++++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/preprocessing/nextclade/src/loculus_preprocessing/__init__.py b/preprocessing/nextclade/src/loculus_preprocessing/__init__.py index 70f8ff079f..6f6072ef48 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/__init__.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/__init__.py @@ -3,3 +3,4 @@ from . import datatypes as datatypes from . import nextclade as nextclade from . import prepro as prepro +from . import nextclade as nextclade diff --git a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py index 841db41d50..738d215156 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py @@ -3,9 +3,12 @@ from collections import defaultdict from collections.abc import Sequence from tempfile import TemporaryDirectory -from typing import Any - +from typing import Any, Tuple import dpath +from .nextclade import ( + download_nextclade_dataset, + enrich_with_nextclade, +) from .backend import ( download_minimizer, @@ -33,6 +36,7 @@ ProcessedMetadataValue, ProcessingAnnotation, ProcessingAnnotationAlignment, + ProcessingAnnotationAlignment, ProcessingResult, ProcessingSpec, SegmentName, @@ -167,7 +171,7 @@ def add_input_metadata( return InputData(datum=unprocessed.inputMetadata[input_path]) -def _call_processing_function( # noqa: PLR0913, PLR0917 +def _call_processing_function( accession_version: AccessionVersion, spec: ProcessingSpec, output_field: str, @@ -189,7 +193,7 @@ def _call_processing_function( # noqa: PLR0913, PLR0917 output_field, input_fields, ) - except Exception as e: + except Exception as e: # noqa: BLE001 msg = f"Processing for spec: {spec} with input data: {input_data} failed with {e}" raise RuntimeError(msg) from e From de5d87a2a95ab12cf6165ef7df467257e3fb748d Mon Sep 17 00:00:00 2001 From: anna-parker <50943381+anna-parker@users.noreply.github.com> Date: Fri, 21 Nov 2025 20:12:39 +0100 Subject: [PATCH 02/44] format --- .../src/loculus_preprocessing/__init__.py | 1 - .../src/loculus_preprocessing/prepro.py | 64 +++++++++++++++++-- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/preprocessing/nextclade/src/loculus_preprocessing/__init__.py b/preprocessing/nextclade/src/loculus_preprocessing/__init__.py index 6f6072ef48..70f8ff079f 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/__init__.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/__init__.py @@ -3,4 +3,3 @@ from . import datatypes as datatypes from . import nextclade as nextclade from . import prepro as prepro -from . import nextclade as nextclade diff --git a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py index 738d215156..4bfa863828 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py @@ -3,12 +3,9 @@ from collections import defaultdict from collections.abc import Sequence from tempfile import TemporaryDirectory -from typing import Any, Tuple +from typing import Any + import dpath -from .nextclade import ( - download_nextclade_dataset, - enrich_with_nextclade, -) from .backend import ( download_minimizer, @@ -193,7 +190,7 @@ def _call_processing_function( output_field, input_fields, ) - except Exception as e: # noqa: BLE001 + except Exception as e: msg = f"Processing for spec: {spec} with input data: {input_data} failed with {e}" raise RuntimeError(msg) from e @@ -243,6 +240,61 @@ def get_sequence_length( return len(sequence) if sequence else 0 +def add_alignment_errors_warnings( + unprocessed: UnprocessedAfterNextclade, + config: Config, + errors: list[ProcessingAnnotation], + warnings: list[ProcessingAnnotation], +) -> tuple[list[ProcessingAnnotation], list[ProcessingAnnotation]]: + if not unprocessed.nextcladeMetadata and unprocessed.unalignedNucleotideSequences: + message = ( + "An unknown internal error occurred while aligning sequences, " + "please contact the administrator." + ) + errors.append( + ProcessingAnnotation.from_single( + "alignment", AnnotationSourceType.NUCLEOTIDE_SEQUENCE, message=message + ) + ) + return (errors, warnings) + aligned_segments = set() + for segment in config.nucleotideSequences: + if segment not in unprocessed.unalignedNucleotideSequences: + continue + if unprocessed.nextcladeMetadata and ( + segment not in unprocessed.nextcladeMetadata + or (unprocessed.nextcladeMetadata[segment] is None) + ): + message = ( + "Nucleotide sequence failed to align" + if not config.multi_segment + else f"Nucleotide sequence for {segment} failed to align" + ) + annotation = ProcessingAnnotation.from_single( + segment, AnnotationSourceType.NUCLEOTIDE_SEQUENCE, message=message + ) + if config.multi_segment and config.alignment_requirement == AlignmentRequirement.ANY: + warnings.append(annotation) + else: + errors.append(annotation) + continue + aligned_segments.add(segment) + + if ( + not aligned_segments + and config.multi_segment + and len(unprocessed.unalignedNucleotideSequences) > 0 + ): + errors.append( + ProcessingAnnotation.from_single( + ProcessingAnnotationAlignment, + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + message="No segment aligned.", + ) + ) + return (errors, warnings) + + def get_output_metadata( accession_version: AccessionVersion, unprocessed: UnprocessedData | UnprocessedAfterNextclade, From d7142064ba5c8926b73409793aa22a938892c7a2 Mon Sep 17 00:00:00 2001 From: anna-parker <50943381+anna-parker@users.noreply.github.com> Date: Fri, 21 Nov 2025 20:18:42 +0100 Subject: [PATCH 03/44] make order of functions a bit cleaner --- .../src/loculus_preprocessing/prepro.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py index 4bfa863828..cbb276802f 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py @@ -498,6 +498,39 @@ def process_single( ) +def processed_entry_no_alignment( + accession_version: AccessionVersion, + unprocessed: UnprocessedData, + output_metadata: ProcessedMetadata, + errors: list[ProcessingAnnotation], + warnings: list[ProcessingAnnotation], +) -> SubmissionData: + """Process a single sequence without alignment""" + + aligned_nucleotide_sequences: dict[SegmentName, NucleotideSequence | None] = {} + aligned_aminoacid_sequences: dict[GeneName, AminoAcidSequence | None] = {} + nucleotide_insertions: dict[SegmentName, list[NucleotideInsertion]] = {} + amino_acid_insertions: dict[GeneName, list[AminoAcidInsertion]] = {} + + return SubmissionData( + processed_entry=ProcessedEntry( + accession=accession_from_str(accession_version), + version=version_from_str(accession_version), + data=ProcessedData( + metadata=output_metadata, + unalignedNucleotideSequences=unprocessed.unalignedNucleotideSequences, + alignedNucleotideSequences=aligned_nucleotide_sequences, + nucleotideInsertions=nucleotide_insertions, + alignedAminoAcidSequences=aligned_aminoacid_sequences, + aminoAcidInsertions=amino_acid_insertions, + ), + errors=errors, + warnings=warnings, + ), + submitter=unprocessed.submitter, + ) + + def process_single_unaligned( accession_version: AccessionVersion, unprocessed: UnprocessedData, From db531bd8061f1217f635b673efe97af26f70f140 Mon Sep 17 00:00:00 2001 From: anna-parker <50943381+anna-parker@users.noreply.github.com> Date: Fri, 21 Nov 2025 20:29:09 +0100 Subject: [PATCH 04/44] make diff cleaner --- .../src/loculus_preprocessing/prepro.py | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py index cbb276802f..4bfa863828 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py @@ -498,39 +498,6 @@ def process_single( ) -def processed_entry_no_alignment( - accession_version: AccessionVersion, - unprocessed: UnprocessedData, - output_metadata: ProcessedMetadata, - errors: list[ProcessingAnnotation], - warnings: list[ProcessingAnnotation], -) -> SubmissionData: - """Process a single sequence without alignment""" - - aligned_nucleotide_sequences: dict[SegmentName, NucleotideSequence | None] = {} - aligned_aminoacid_sequences: dict[GeneName, AminoAcidSequence | None] = {} - nucleotide_insertions: dict[SegmentName, list[NucleotideInsertion]] = {} - amino_acid_insertions: dict[GeneName, list[AminoAcidInsertion]] = {} - - return SubmissionData( - processed_entry=ProcessedEntry( - accession=accession_from_str(accession_version), - version=version_from_str(accession_version), - data=ProcessedData( - metadata=output_metadata, - unalignedNucleotideSequences=unprocessed.unalignedNucleotideSequences, - alignedNucleotideSequences=aligned_nucleotide_sequences, - nucleotideInsertions=nucleotide_insertions, - alignedAminoAcidSequences=aligned_aminoacid_sequences, - aminoAcidInsertions=amino_acid_insertions, - ), - errors=errors, - warnings=warnings, - ), - submitter=unprocessed.submitter, - ) - - def process_single_unaligned( accession_version: AccessionVersion, unprocessed: UnprocessedData, From 2d482dbe25687e24ebafa628e7546acbd654f394 Mon Sep 17 00:00:00 2001 From: anna-parker <50943381+anna-parker@users.noreply.github.com> Date: Mon, 24 Nov 2025 11:49:59 +0100 Subject: [PATCH 05/44] format --- preprocessing/nextclade/src/loculus_preprocessing/prepro.py | 1 - 1 file changed, 1 deletion(-) diff --git a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py index 4bfa863828..bfb76c8d2e 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py @@ -33,7 +33,6 @@ ProcessedMetadataValue, ProcessingAnnotation, ProcessingAnnotationAlignment, - ProcessingAnnotationAlignment, ProcessingResult, ProcessingSpec, SegmentName, From 892a22618b17178dc58488663b98d1d0def6b65b Mon Sep 17 00:00:00 2001 From: Cornelius Roemer Date: Mon, 24 Nov 2025 13:16:29 +0100 Subject: [PATCH 06/44] pass less through function --- preprocessing/nextclade/src/loculus_preprocessing/prepro.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py index bfb76c8d2e..5fb711156c 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py @@ -167,7 +167,7 @@ def add_input_metadata( return InputData(datum=unprocessed.inputMetadata[input_path]) -def _call_processing_function( +def _call_processing_function( # noqa: PLR0913, PLR0917 accession_version: AccessionVersion, spec: ProcessingSpec, output_field: str, From 8ae87381d7bc2aa20a197312dfe6f4611b73e6af Mon Sep 17 00:00:00 2001 From: anna-parker <50943381+anna-parker@users.noreply.github.com> Date: Thu, 6 Nov 2025 11:25:04 +0100 Subject: [PATCH 07/44] feat: update edit page --- .../tests/pages/submission.page.ts | 4 +- .../specs/features/revise-sequence.spec.ts | 14 +- website/src/components/Edit/EditPage.spec.tsx | 9 +- website/src/components/Edit/EditPage.tsx | 7 +- .../components/Edit/SequencesForm.spec.tsx | 223 +++++++++++++++--- website/src/components/Edit/SequencesForm.tsx | 162 ++++++++----- .../Submission/FileUpload/fileProcessing.ts | 19 +- .../Submission/FormOrUploadWrapper.spec.tsx | 14 +- .../Submission/FormOrUploadWrapper.tsx | 4 +- .../Submission/SubmissionForm.spec.tsx | 6 +- .../edit/[accession]/[version].astro | 5 +- 11 files changed, 340 insertions(+), 127 deletions(-) diff --git a/integration-tests/tests/pages/submission.page.ts b/integration-tests/tests/pages/submission.page.ts index 65d53c046b..02f71bc10b 100644 --- a/integration-tests/tests/pages/submission.page.ts +++ b/integration-tests/tests/pages/submission.page.ts @@ -127,10 +127,10 @@ export class SingleSequenceSubmissionPage extends SubmissionPage { async fillSequenceData(sequenceData: Record) { for (const [key, value] of Object.entries(sequenceData)) { - await this.page.getByLabel(`${key} segment file`).setInputFiles({ + await this.page.getByLabel(new RegExp('Add a segment', 'i')).setInputFiles({ name: 'example.txt', mimeType: 'text/plain', - buffer: Buffer.from(value), + buffer: Buffer.from(`>${key}\n${value}`), }); } } diff --git a/integration-tests/tests/specs/features/revise-sequence.spec.ts b/integration-tests/tests/specs/features/revise-sequence.spec.ts index 7e734842dd..3838442ce5 100644 --- a/integration-tests/tests/specs/features/revise-sequence.spec.ts +++ b/integration-tests/tests/specs/features/revise-sequence.spec.ts @@ -39,13 +39,13 @@ sequenceTest( await page.getByRole('link', { name: 'Revise this sequence' }).click({ timeout: 15000 }); await expect(page.getByRole('heading', { name: 'Create new revision from' })).toBeVisible(); - await page.getByTestId('discard_L_segment_file').click(); - await page.getByTestId('discard_S_segment_file').click(); - await page.getByTestId('S_segment_file').setInputFiles({ - name: 'update_S.txt', - mimeType: 'text/plain', - buffer: Buffer.from('AAAAA'), - }); + await page.getByTestId('discard_L_segment_file').click(); + await page.getByTestId('discard_S_segment_file').click(); + await page.getByTestId('Add a segment_segment_file').setInputFiles({ + name: 'update_S.txt', + mimeType: 'text/plain', + buffer: Buffer.from('>S\nAAAAA'), + }); await page.getByRole('button', { name: 'Submit' }).click(); await page.getByRole('button', { name: 'Confirm' }).click(); diff --git a/website/src/components/Edit/EditPage.spec.tsx b/website/src/components/Edit/EditPage.spec.tsx index dd2681f361..424ba93901 100644 --- a/website/src/components/Edit/EditPage.spec.tsx +++ b/website/src/components/Edit/EditPage.spec.tsx @@ -14,6 +14,7 @@ import { } from '../../../vitest.setup.ts'; import { type UnprocessedMetadataRecord } from '../../types/backend.ts'; import type { InputField } from '../../types/config.ts'; +import { SINGLE_REFERENCE } from '../../types/referencesGenomes.ts'; import type { ClientConfig } from '../../types/runtimeConfig.ts'; const queryClient = new QueryClient(); @@ -41,7 +42,13 @@ function renderEditPage({ ; submissionDataTypes: SubmissionDataTypes; @@ -45,7 +46,7 @@ function getErrorDetail(error: unknown): string { const InnerEditPage: FC = ({ organism, dataToEdit, - segmentNames, + referenceGenomeLightweightSchema, clientConfig, accessToken, groupedInputFields, @@ -53,7 +54,7 @@ const InnerEditPage: FC = ({ }) => { const [editableMetadata, setEditableMetadata] = useState(EditableMetadata.fromInitialData(dataToEdit)); const [editableSequences, setEditableSequences] = useState( - EditableSequences.fromInitialData(dataToEdit, segmentNames), + EditableSequences.fromInitialData(dataToEdit, referenceGenomeLightweightSchema), ); const isCreatingRevision = dataToEdit.status === approvedForReleaseStatus; diff --git a/website/src/components/Edit/SequencesForm.spec.tsx b/website/src/components/Edit/SequencesForm.spec.tsx index 9b98f3feb7..40d908fa00 100644 --- a/website/src/components/Edit/SequencesForm.spec.tsx +++ b/website/src/components/Edit/SequencesForm.spec.tsx @@ -2,65 +2,220 @@ import { describe, expect, test } from 'vitest'; import { EditableSequences } from './SequencesForm'; import { defaultReviewData } from '../../../vitest.setup'; +import { type ReferenceGenomesLightweightSchema, SINGLE_REFERENCE } from '../../types/referencesGenomes.ts'; +function makeReferenceGenomeLightweightSchema(nucleotideSegmentNames: string[]): ReferenceGenomesLightweightSchema { + return { + [SINGLE_REFERENCE]: { + nucleotideSegmentNames, + geneNames: [], + insdcAccessionFull: [], + }, + }; +} + +function makeSubOrganismReferenceSchema(suborganisms: string[]): ReferenceGenomesLightweightSchema { + const result: ReferenceGenomesLightweightSchema = {}; + + for (const suborganism of suborganisms) { + result[suborganism] = { + nucleotideSegmentNames: ['main'], + geneNames: [], + insdcAccessionFull: [], + }; + } + + return result; +} + +/* eslint-disable @typescript-eslint/naming-convention -- this test has keys that expectedly contain spaces */ describe('SequencesForm', () => { - test('Empty editable sequences (with names only) produces `undefined`', () => { - const emptyEditableSequences = EditableSequences.fromSequenceNames(['foo', 'bar']); + test('Empty editable sequences produces no output', () => { + const emptyEditableSequences = EditableSequences.fromSequenceNames( + makeReferenceGenomeLightweightSchema(['foo', 'bar']), + ); + expect(emptyEditableSequences.getSequenceFasta('subId')).toBeUndefined(); + expect(emptyEditableSequences.getSequenceRecord()).deep.equals({}); }); - test('GIVEN a multi-segmented organism and only a single segment has data THEN the fasta has only that segment', async () => { - let editableSequences = EditableSequences.fromSequenceNames(['foo', 'bar']); - editableSequences = editableSequences.update({ key: 'foo', value: 'ATCG' }); - const fasta = editableSequences.getSequenceFasta('subId'); - expect(fasta).not.toBeUndefined(); - const fastaText = await fasta!.text(); - expect.soft(fastaText).toBe('>subId_foo\nATCG'); + test('GIVEN organism with 2 suborganisms with 1 segment each THEN allows at max 1 inputs', async () => { + let editableSequences = EditableSequences.fromSequenceNames( + makeSubOrganismReferenceSchema(['suborg1', 'suborg2']), + ); + const initialRows = editableSequences.rows; + expect(initialRows).toEqual([ + { label: 'Add a segment', value: null, initialValue: null, key: expect.any(String) }, + ]); + const firstKey = initialRows[0].key; + { + editableSequences = editableSequences.update(firstKey, 'ATCG', 'Segment 1'); + const fasta = editableSequences.getSequenceFasta('subId'); + expect(fasta).not.toBeUndefined(); + const fastaText = await fasta!.text(); + expect.soft(fastaText).toBe('>subId\nATCG'); + expect(editableSequences.getSequenceRecord()).deep.equals({ 'Segment 1': 'ATCG' }); - const sequenceRecord = Object.entries(editableSequences.getSequenceRecord()); - expect(sequenceRecord.length).toBe(1); - expect(sequenceRecord[0]).toStrictEqual(['foo', 'ATCG']); + const rows = editableSequences.rows; + expect(rows).toEqual([{ label: 'Segment 1', value: 'ATCG', initialValue: null, key: firstKey }]); + } + expect(() => editableSequences.update('another key', 'GG', 'another key')).toThrowError( + 'Maximum limit reached — you can add up to 1 sequence file(s) only.', + ); + editableSequences = editableSequences.update(firstKey, null, null); + expect(editableSequences.rows).toEqual([ + { label: 'Add a segment', value: null, initialValue: null, key: expect.any(String) }, + ]); + const rowsAfterDeletion = editableSequences.rows; + const newFirstKey = rowsAfterDeletion[0].key; + { + editableSequences = editableSequences.update(newFirstKey, 'ATCG', 'Segment 1'); + const fasta = editableSequences.getSequenceFasta('subId'); + expect(fasta).not.toBeUndefined(); + const fastaText = await fasta!.text(); + expect.soft(fastaText).toBe('>subId\nATCG'); + expect(editableSequences.getSequenceRecord()).deep.equals({ 'Segment 1': 'ATCG' }); + + const rows = editableSequences.rows; + expect(rows).toEqual([{ label: 'Segment 1', value: 'ATCG', initialValue: null, key: newFirstKey }]); + } }); - test('GIVEN a single-segmented organism with segment data THEN the fasta header does not contain the segment name', async () => { - let editableSequences = EditableSequences.fromSequenceNames(['foo']); - editableSequences = editableSequences.update({ key: 'foo', value: 'ATCG' }); + test('GIVEN organism with 2 segments THEN allows at max 2 inputs', async () => { + let editableSequences = EditableSequences.fromSequenceNames( + makeReferenceGenomeLightweightSchema(['foo', 'bar']), + ); + + const initialRows = editableSequences.rows; + expect(initialRows).toEqual([ + { label: 'Add a segment', value: null, initialValue: null, key: expect.any(String) }, + ]); + const firstKey = initialRows[0].key; + + let secondKey; + { + editableSequences = editableSequences.update(firstKey, 'ATCG', 'Segment 1'); + const fasta = editableSequences.getSequenceFasta('subId'); + expect(fasta).not.toBeUndefined(); + const fastaText = await fasta!.text(); + expect.soft(fastaText).toBe('>subId_Segment1\nATCG'); + expect(editableSequences.getSequenceRecord()).deep.equals({ 'Segment 1': 'ATCG' }); + + const rows = editableSequences.rows; + expect(rows).toEqual([ + { label: 'Segment 1', value: 'ATCG', initialValue: null, key: firstKey }, + { label: 'Add a segment', value: null, initialValue: null, key: expect.any(String) }, + ]); + secondKey = rows[1].key; + } + + { + editableSequences = editableSequences.update(secondKey, 'TT', 'Segment 2'); + const fasta = editableSequences.getSequenceFasta('subId'); + expect(fasta).not.toBeUndefined(); + const fastaText = await fasta!.text(); + expect.soft(fastaText).toBe('>subId_Segment1\nATCG\n>subId_Segment2\nTT'); + expect(editableSequences.getSequenceRecord()).deep.equals({ 'Segment 1': 'ATCG', 'Segment 2': 'TT' }); + + const rows = editableSequences.rows; + expect(rows).deep.equals([ + { label: 'Segment 1', value: 'ATCG', initialValue: null, key: firstKey }, + { label: 'Segment 2', value: 'TT', initialValue: null, key: secondKey }, + ]); + } + + expect(() => editableSequences.update('another key', 'GG', 'another key')).toThrowError( + 'Maximum limit reached — you can add up to 2 sequence file(s) only.', + ); + }); + + test('GIVEN a single-segmented organism THEN only allows 1 input and fasta header does not contain the segment name', async () => { + let editableSequences = EditableSequences.fromSequenceNames(makeReferenceGenomeLightweightSchema(['foo'])); + + const initialRows = editableSequences.rows; + expect(initialRows).toEqual([ + { label: 'Add a segment', value: null, initialValue: null, key: expect.any(String) }, + ]); + const key = initialRows[0].key; + + editableSequences = editableSequences.update(key, 'ATCG', key); const fasta = editableSequences.getSequenceFasta('subId'); expect(fasta).not.toBeUndefined(); const fastaText = await fasta!.text(); expect.soft(fastaText).toBe('>subId\nATCG'); - const sequenceRecord = Object.entries(editableSequences.getSequenceRecord()); - expect(sequenceRecord.length).toBe(1); - expect(sequenceRecord[0]).toStrictEqual(['foo', 'ATCG']); + expect(editableSequences.getSequenceRecord()).deep.equals({ [key]: 'ATCG' }); + + const rows = editableSequences.rows; + expect(rows).deep.equals([{ label: key, value: 'ATCG', initialValue: null, key }]); + + expect(() => editableSequences.update('another key', 'GG', 'another key')).toThrowError( + 'Maximum limit reached — you can add up to 1 sequence file(s) only.', + ); }); - test('GIVEN initial data with an empty segment THEN the fasta does not contain the empty segment', async () => { - let editableSequences = EditableSequences.fromInitialData(defaultReviewData, [ - 'originalSequenceName', - 'anotherSequenceName', + test('GIVEN no initial data WHEN I add and remove a sequence THEN input is also removed again', () => { + let editableSequences = EditableSequences.fromSequenceNames( + makeReferenceGenomeLightweightSchema(['foo', 'bar']), + ); + + const key = editableSequences.rows[0].key; + + editableSequences = editableSequences.update(key, 'ATCG', key); + expect(editableSequences.rows).toEqual([ + { label: key, value: 'ATCG', initialValue: null, key }, + { label: 'Add a segment', value: null, initialValue: null, key: expect.any(String) }, ]); - editableSequences = editableSequences.update({ key: 'originalSequenceName', value: 'ATCG' }); + + editableSequences = editableSequences.update(key, null, null); + expect(editableSequences.rows).toEqual([ + { label: 'Add a segment', value: null, initialValue: null, key: expect.any(String) }, + ]); + }); + + test('GIVEN initial data with an empty segment THEN the fasta does not contain the empty segment', async () => { + let editableSequences = EditableSequences.fromInitialData( + defaultReviewData, + makeReferenceGenomeLightweightSchema(['originalSequenceName', 'anotherSequenceName']), + ); + editableSequences = editableSequences.update(editableSequences.rows[0].key, 'ATCG', 'label'); const fasta = editableSequences.getSequenceFasta('subId'); expect(fasta).not.toBeUndefined(); const fastaText = await fasta!.text(); - expect.soft(fastaText).toBe('>subId_originalSequenceName\nATCG'); + expect.soft(fastaText).toBe('>subId_label\nATCG'); - const sequenceRecord = Object.entries(editableSequences.getSequenceRecord()); - expect(sequenceRecord.length).toBe(1); - expect(sequenceRecord[0]).toStrictEqual(['originalSequenceName', 'ATCG']); + expect(editableSequences.getSequenceRecord()).deep.equals({ label: 'ATCG' }); }); - test('GIVEN initial segment data that is then deleted as an edit THEN the edit record does not contain the segment key', () => { - let editableSequences = EditableSequences.fromInitialData(defaultReviewData, [ - 'originalSequenceName', - 'anotherSequenceName', + test('GIVEN initial segment data that is then deleted as an edit THEN the edit record does not contain the segment key but input field is kept', () => { + let editableSequences = EditableSequences.fromInitialData( + defaultReviewData, + makeReferenceGenomeLightweightSchema(['originalSequenceName', 'anotherSequenceName']), + ); + + expect(editableSequences.rows).toEqual([ + { + label: 'originalSequenceName', + value: 'originalUnalignedNucleotideSequencesValue', + initialValue: 'originalUnalignedNucleotideSequencesValue', + key: expect.any(String), + }, + { label: 'Add a segment', value: null, initialValue: null, key: expect.any(String) }, ]); - editableSequences = editableSequences.update({ key: 'originalSequenceName', value: '' }); + + editableSequences = editableSequences.update(editableSequences.rows[0].key, null, null); const fasta = editableSequences.getSequenceFasta('subId'); expect(fasta).toBeUndefined(); - const sequenceRecord = Object.entries(editableSequences.getSequenceRecord()); - expect(sequenceRecord.length).toBe(0); + expect(editableSequences.getSequenceRecord()).deep.equals({}); + + expect(editableSequences.rows).toEqual([ + { + label: 'Add a segment', + value: null, + initialValue: null, + key: expect.any(String), + }, + ]); }); }); diff --git a/website/src/components/Edit/SequencesForm.tsx b/website/src/components/Edit/SequencesForm.tsx index 3af8c91a2b..ef27d814a2 100644 --- a/website/src/components/Edit/SequencesForm.tsx +++ b/website/src/components/Edit/SequencesForm.tsx @@ -1,7 +1,7 @@ import { type Dispatch, type FC, type SetStateAction } from 'react'; -import { type KeyValuePair, type Row } from './InputField'; -import { mapErrorsAndWarnings, type SequenceEntryToEdit } from '../../types/backend.ts'; +import { type SequenceEntryToEdit } from '../../types/backend.ts'; +import type { ReferenceGenomesLightweightSchema } from '../../types/referencesGenomes.ts'; import { FileUploadComponent } from '../Submission/FileUpload/FileUploadComponent.tsx'; import { PLAIN_SEGMENT_KIND, VirtualFile } from '../Submission/FileUpload/fileProcessing.ts'; @@ -29,80 +29,122 @@ function generateAndDownloadFastaFile( URL.revokeObjectURL(url); } +type EditableSequenceFile = { + key: string; + label: string; + value: string | null; + initialValue: string | null; +}; + export class EditableSequences { - private constructor(public readonly rows: Row[]) {} - - private static emptyRows(names: string[]): Row[] { - return names.map((name) => ({ - key: name, - initialValue: '', - value: '', - errors: [], - warnings: [], + private static nextKey = 0; + + private readonly editableSequenceFiles: EditableSequenceFile[]; + private readonly maxNumberOfRows: number; + + public get rows(): Required[] { + const rows = this.editableSequenceFiles.map((row, _) => ({ + ...row, + label: row.label, })); + if (rows.length < this.maxNumberOfRows) { + rows.push({ + label: `Add a segment`, + value: null, + initialValue: null, + key: EditableSequences.getNextKey(), + }); + } + return rows; + } + + private constructor(rows: EditableSequenceFile[], maxNumberOfRows: number) { + this.editableSequenceFiles = rows; + this.maxNumberOfRows = maxNumberOfRows; } - private isMultsegmented() { - return this.rows.length > 1; + isMultiSegmented() { + return this.maxNumberOfRows > 1; } /** * @param initialData The sequence entry to edit, from which the initial sequence data is taken. - * @param segmentNames All segment names for the organism of the sequence. This is used to include empty segments. + * @param referenceGenomeLightweightSchema */ - static fromInitialData(initialData: SequenceEntryToEdit, segmentNames: string[]): EditableSequences { - const emptyRows = this.emptyRows(segmentNames); + static fromInitialData( + initialData: SequenceEntryToEdit, + referenceGenomeLightweightSchema: ReferenceGenomesLightweightSchema, + ): EditableSequences { const existingDataRows = Object.entries(initialData.originalData.unalignedNucleotideSequences).map( ([key, value]) => ({ - key, - initialValue: value, + label: key, value: value, - ...mapErrorsAndWarnings(initialData, key, 'NucleotideSequence'), + initialValue: value, + key: EditableSequences.getNextKey(), //TODO: check if this is buggy }), ); - const mergedRows: Row[] = []; - // merge in this way to retain the order of segment names as they were given. - emptyRows.forEach((row) => { - const existingRow = existingDataRows.find((r) => r.key === row.key); - if (existingRow) { - mergedRows.push(existingRow); - } else { - mergedRows.push(row); - } - }); - return new EditableSequences(mergedRows); + return new EditableSequences(existingDataRows, this.getMaxNumberOfRows(referenceGenomeLightweightSchema)); } /** * Create an empty {@link EditableSequences} object from segment names. * Each segment will be empty initially. */ - static fromSequenceNames(segmentNames: string[]): EditableSequences { - return new EditableSequences(this.emptyRows(segmentNames)); + static fromSequenceNames(referenceGenomeLightweightSchema: ReferenceGenomesLightweightSchema): EditableSequences { + return new EditableSequences([], this.getMaxNumberOfRows(referenceGenomeLightweightSchema)); + } + + private static getMaxNumberOfRows(referenceGenomeLightweightSchema: ReferenceGenomesLightweightSchema): number { + return Math.max( + ...Object.values(referenceGenomeLightweightSchema).map( + (suborganismSchema) => suborganismSchema.nucleotideSegmentNames.length, + ), + ); + } + + private static getNextKey(): string { + return (EditableSequences.nextKey++).toString(); } /** * Create a new {@link EditableSequences} object with the given row value updated. */ - update(editedRow: KeyValuePair & {}): EditableSequences { + update(key: string, value: string | null, label: string | null): EditableSequences { + const existingFileIndex = this.editableSequenceFiles.findIndex((file) => file.key === key); + + if (existingFileIndex === -1 && this.editableSequenceFiles.length === this.maxNumberOfRows) { + throw new Error(`Maximum limit reached — you can add up to ${this.maxNumberOfRows} sequence file(s) only.`); + } + + label ??= value == null ? 'Add a segment' : key; + + const newSequenceFiles = [...this.editableSequenceFiles]; + newSequenceFiles[existingFileIndex > -1 ? existingFileIndex : this.editableSequenceFiles.length] = { + ...(existingFileIndex > -1 ? newSequenceFiles[existingFileIndex] : { key, initialValue: null }), + value: value, + label: label, + }; + return new EditableSequences( - this.rows.map((prevRow) => - prevRow.key === editedRow.key ? { ...prevRow, value: editedRow.value.trim() } : prevRow, - ), + newSequenceFiles.filter((file) => file.value !== null), + this.maxNumberOfRows, ); } getSequenceFasta(submissionId: string): File | undefined { - const filledRows = this.rows.filter((row) => row.value.trim() !== ''); + const filledRows = this.rows.filter((row) => row.value !== null); - // if no values are set at all, return undefined - if (filledRows.length === 0) return undefined; + if (filledRows.length === 0) { + return undefined; + } - const fastaContent = !this.isMultsegmented() + const fastaContent = !this.isMultiSegmented() ? `>${submissionId}\n${filledRows[0].value}` : filledRows - .map((sequence) => `>${submissionId}_${sequence.key}\n${sequence.value}`) - .filter(Boolean) + .map( + (sequence) => + `>${submissionId}_${sequence.label.replaceAll(/[^a-zA-Z0-9]/g, '')}\n${sequence.value}`, + ) .join('\n'); return new File([fastaContent], 'sequences.fasta', { type: 'text/plain' }); @@ -110,8 +152,8 @@ export class EditableSequences { getSequenceRecord(): Record { return this.rows - .filter((row) => row.value.trim() !== '') - .reduce((prev, row) => ({ ...prev, [row.key]: row.value }), {}); + .filter((row) => row.value !== null) + .reduce((prev, row) => ({ ...prev, [row.label]: row.value }), {}); } } @@ -127,45 +169,43 @@ export const SequencesForm: FC = ({ dataToEdit, isLoading, }) => { - const singleSegment = editableSequences.rows.length === 1; + const multiSegment = editableSequences.isMultiSegmented(); return ( <> -

{`Nucleotide sequence${singleSegment ? '' : 's'}`}

+

{`Nucleotide sequence${multiSegment ? 's' : ''}`}

{editableSequences.rows.map((field) => (
- {!singleSegment && ( - + {multiSegment && ( + )} { - const text = file ? await file.text() : ''; + const text = file ? await file.text() : null; + const header = file ? await file.header() : null; setEditableSequences((editableSequences) => - editableSequences.update({ - key: field.key, - value: text, - }), + editableSequences.update(field.key, text, header), ); }} - name={`${field.key}_segment_file`} - ariaLabel={`${field.key} Segment File`} + name={`${field.label}_segment_file`} + ariaLabel={`${field.label} Segment File`} fileKind={PLAIN_SEGMENT_KIND} small={true} initialValue={ - field.initialValue.length > 0 + field.initialValue !== null ? new VirtualFile(field.initialValue, 'Existing data') : undefined } - showUndo={true} + showUndo={field.initialValue !== null} onDownload={ - field.initialValue.length > 0 && dataToEdit + field.initialValue !== null && dataToEdit ? () => { const accessionVersion = `${dataToEdit.accession}.${dataToEdit.version}`; generateAndDownloadFastaFile( accessionVersion, - field.initialValue, - field.key, - singleSegment, + field.value ?? '', + field.label, + !multiSegment, ); } : undefined diff --git a/website/src/components/Submission/FileUpload/fileProcessing.ts b/website/src/components/Submission/FileUpload/fileProcessing.ts index e84e0ae675..b79087e1d9 100644 --- a/website/src/components/Submission/FileUpload/fileProcessing.ts +++ b/website/src/components/Submission/FileUpload/fileProcessing.ts @@ -87,12 +87,14 @@ export const PLAIN_SEGMENT_KIND: FileKind = { ), ); } - const headerLineCount = lines.filter((l) => l.startsWith('>')).length; - if (headerLineCount > 1) { + + const headerLines = lines.filter((l) => l.startsWith('>')); + if (headerLines.length > 1) { return err( - new Error(`Found ${headerLineCount} headers in uploaded file, only a single header is allowed.`), + new Error(`Found ${headerLines.length} headers in uploaded file, only a single header is allowed.`), ); } + const header = headerLines.length === 1 ? headerLines[0].substring(1).trim() : null; const segmentData = lines .filter((l) => !l.startsWith('>')) .map((l) => l.trim()) @@ -108,6 +110,7 @@ export const PLAIN_SEGMENT_KIND: FileKind = { text: () => Promise.resolve(segmentData), handle: () => file, warnings: () => [], + header: () => Promise.resolve(header), }); }, }; @@ -118,6 +121,8 @@ export interface ProcessedFile { text(): Promise; + header(): Promise; + /* The handle to the file on disk. */ handle(): File; @@ -142,6 +147,10 @@ export class RawFile implements ProcessedFile { return this.innerFile.text(); } + header(): Promise { + return Promise.resolve(null); + } + warnings(): string[] { return []; } @@ -266,6 +275,10 @@ export class ExcelFile implements ProcessedFile { return this.originalFile; } + header(): Promise { + return Promise.resolve(null); + } + warnings(): string[] { return this.processingWarnings; } diff --git a/website/src/components/Submission/FormOrUploadWrapper.spec.tsx b/website/src/components/Submission/FormOrUploadWrapper.spec.tsx index 9ac414f62a..c09e206a96 100644 --- a/website/src/components/Submission/FormOrUploadWrapper.spec.tsx +++ b/website/src/components/Submission/FormOrUploadWrapper.spec.tsx @@ -1,4 +1,4 @@ -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { useState } from 'react'; import { describe, expect, test, vi } from 'vitest'; @@ -101,11 +101,13 @@ describe('FormOrUploadWrapper', () => { expect(input).toHaveValue(value); } - async function uploadSegmentData(segment: string, data: string) { - const file = new File([data], 'foo.txt', { type: 'text/plain' }); - await userEvent.upload(screen.getByLabelText(new RegExp(`${segment} segment file`, 'i')), file); + async function uploadSegmentData(fastaHeader: string, data: string) { + const fasta = `>${fastaHeader}\n${data}`; + const file = new File([fasta], 'foo.fasta', { type: 'text/plain' }); + const uploadInput = () => screen.getByLabelText(new RegExp('Add a segment', 'i')); + await waitFor(() => expect(uploadInput()).toBeVisible()); + await userEvent.upload(uploadInput(), file); } - test('renders all metadata fields and sequence segments', () => { renderForm(true); expect(screen.getByText(/ID/)).toBeTruthy(); @@ -114,8 +116,6 @@ describe('FormOrUploadWrapper', () => { const collectionCountryLabel = document.querySelector('label[for="collectionCountry"]'); expect(collectionCountryLabel).toHaveTextContent('Collection country'); expect(screen.getByText(/Host/)).toBeTruthy(); - expect(screen.getByText(/foo/)).toBeTruthy(); - expect(screen.getByText(/bar/)).toBeTruthy(); }); test('error when nothing is entered', async () => { diff --git a/website/src/components/Submission/FormOrUploadWrapper.tsx b/website/src/components/Submission/FormOrUploadWrapper.tsx index f85c438c66..40e84afc95 100644 --- a/website/src/components/Submission/FormOrUploadWrapper.tsx +++ b/website/src/components/Submission/FormOrUploadWrapper.tsx @@ -67,9 +67,7 @@ export const FormOrUploadWrapper: FC = ({ getFirstLightweightSchema(referenceGenomeLightweightSchema).nucleotideSegmentNames.length > 1; const [editableMetadata, setEditableMetadata] = useState(EditableMetadata.empty()); const [editableSequences, setEditableSequences] = useState( - EditableSequences.fromSequenceNames( - getFirstLightweightSchema(referenceGenomeLightweightSchema).nucleotideSegmentNames, - ), + EditableSequences.fromSequenceNames(referenceGenomeLightweightSchema), ); const [metadataFile, setMetadataFile] = useState(undefined); diff --git a/website/src/components/Submission/SubmissionForm.spec.tsx b/website/src/components/Submission/SubmissionForm.spec.tsx index 7cd0639938..119d9b4c85 100644 --- a/website/src/components/Submission/SubmissionForm.spec.tsx +++ b/website/src/components/Submission/SubmissionForm.spec.tsx @@ -117,7 +117,7 @@ describe('SubmitForm', () => { case 'form': { await userEvent.type(getByLabelText(/ID/), 'myId'); await userEvent.type(getByLabelText(/Foo/), 'foo'); - await userEvent.upload(getByLabelText(/main segment file/i), sequenceFile); + await userEvent.upload(getByLabelText(/Add a segment/i), sequenceFile); break; } case 'bulk': { @@ -201,7 +201,7 @@ describe('SubmitForm', () => { const { getByLabelText, getByRole } = renderSubmissionForm({ inputMode: 'form' }); await userEvent.type(getByLabelText(/ID/), 'myId'); - await userEvent.upload(getByLabelText(/main segment file/i), sequenceFile); + await userEvent.upload(getByLabelText(/Add a segment/i), sequenceFile); await userEvent.click( getByLabelText(/I confirm I have not and will not submit this data independently to INSDC/i), ); @@ -338,7 +338,7 @@ describe('SubmitForm', () => { case 'form': { await userEvent.type(getByLabelText(/ID/), 'myId'); await userEvent.type(getByLabelText(/Foo/), 'foo'); - await userEvent.upload(getByLabelText(/main segment file/i), sequenceFile); + await userEvent.upload(getByLabelText(/Add a segment/i), sequenceFile); break; } case 'bulk': { diff --git a/website/src/pages/[organism]/submission/edit/[accession]/[version].astro b/website/src/pages/[organism]/submission/edit/[accession]/[version].astro index f6c453c935..40e93ca749 100644 --- a/website/src/pages/[organism]/submission/edit/[accession]/[version].astro +++ b/website/src/pages/[organism]/submission/edit/[accession]/[version].astro @@ -9,7 +9,6 @@ import { } from '../../../../../config'; import BaseLayout from '../../../../../layouts/BaseLayout.astro'; import { createBackendClient } from '../../../../../services/backendClientFactory'; -import { getFirstLightweightSchema } from '../../../../../types/referencesGenomes'; import { getAccessToken } from '../../../../../utils/getAccessToken'; const version = Astro.params.version!; @@ -30,7 +29,7 @@ const accessToken = getAccessToken(Astro.locals.session)!; const clientConfig = getRuntimeConfig().public; const schema = getSchema(organism); -const segmentNames = getFirstLightweightSchema(getReferenceGenomeLightweightSchema(organism)).nucleotideSegmentNames; +const referenceGenomeLightweightSchema = getReferenceGenomeLightweightSchema(organism); const dataToEdit = await createBackendClient().getDataToEdit(organism, accessToken, accession, version); --- @@ -43,7 +42,7 @@ const dataToEdit = await createBackendClient().getDataToEdit(organism, accessTok organism={organism} accessToken={accessToken} dataToEdit={dataToEdit} - segmentNames={segmentNames} + referenceGenomeLightweightSchema={referenceGenomeLightweightSchema} clientConfig={clientConfig} groupedInputFields={groupedInputFields} submissionDataTypes={schema.submissionDataTypes} From ee34d484ea4670d09cf95e148e3ac197d6a604e5 Mon Sep 17 00:00:00 2001 From: "Anna (Anya) Parker" <50943381+anna-parker@users.noreply.github.com> Date: Mon, 10 Nov 2025 09:32:25 +0100 Subject: [PATCH 08/44] feat: clean up edit page PR a bit (#5396) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolves # When submitting via the single page submission users now need to submit with the fastaHeader in the format `submissionId_segment` - i.e. the same requirements we have for the multi segment submission. - [ ] All necessary documentation has been adapted. - [ ] The implemented feature is covered by appropriate, automated tests. - [ ] Any manual testing that has been done is documented (i.e. what exactly was tested?) 🚀 Preview: https://edit-page-anya-2.loculus.org --- .../tests/fixtures/sequence.fixture.ts | 12 ++- integration-tests/tests/readonly.setup.ts | 60 +++++-------- .../specs/features/revise-sequence.spec.ts | 22 ++--- .../search/override-hidden-fields.spec.ts | 4 +- .../features/sequence-view.dependent.spec.ts | 16 +++- .../specs/features/single-submit.spec.ts | 12 ++- .../specs/features/submission-flow.spec.ts | 15 +++- .../tests/specs/features/trim-ns.spec.ts | 3 +- .../components/Edit/SequencesForm.spec.tsx | 88 +++++++++++-------- website/src/components/Edit/SequencesForm.tsx | 87 ++++++++++-------- .../Submission/FormOrUploadWrapper.tsx | 2 +- 11 files changed, 176 insertions(+), 145 deletions(-) diff --git a/integration-tests/tests/fixtures/sequence.fixture.ts b/integration-tests/tests/fixtures/sequence.fixture.ts index ae9a8bf1b6..ea0b97659d 100644 --- a/integration-tests/tests/fixtures/sequence.fixture.ts +++ b/integration-tests/tests/fixtures/sequence.fixture.ts @@ -23,11 +23,17 @@ export const test = groupTest.extend({ collectionDate: '2021-10-15', authorAffiliations: 'Test Institute, France', }); + const fastaHeaderL = `${submissionId}_L`; + const fastaHeaderM = `${submissionId}_M`; + const fastaHeaderS = `${submissionId}_S`; await submissionPage.fillSequenceData({ - L: 'CCACATTGACACAGANAGCTCCAGTAGTGGTTCTCTGTCCTTATTAAACCATGGACTTCTTAAGAAACCTTGACTGGACTCAGGTGATTGCTAGTCAGTATGTGACCAATCCCAGGTTTAATATCTCTGATTACTTCGAGATTGTTCGACAGCCTGGTGACGGGAACTGTTTCTACCACAGTATAGCTGAGTTAACCATGCCCAACAAAACAGATCACTCATACCATAACATCAAACATCTGACTGAGGTGGCAGCACGGAAGTATTATCAGGAGGAGCCGGAGGCTAAGCTCATTGGCCTGAGTCTGGAAGACTATCTTAAGAGGATGCTATCTGACAACGAATGGGGATCGACTCTTGAGGCATCTATGTTGGCTAAGGAAATGGGTATTACTATCATCATTTGGACTGTTGCAGCCAGTGACGAAGTGGAAGCAGGCATAAAGTTTGGTGATGGTGATGTGTTTACAGCCGTGAATCTTCTGCACTCCGGACAGACACACTTTGATGCCCTCAGAATACTGCCNCANTTTGAGGCTGACACAAGAGAGNCCTTNAGTCTGGTAGACAANNTNATAGCTGTGGACCANNTGACCTCNTCTTCAAGTGATGAANTGCAGGACTANGAAGANCTTGCTTTAGCACTTACNAGNGCGGAAGAACCATNTAGACGGTCTAGCNTGGATGAGGTNACCCTNTCTAAGAAACAAGCAGAGNTATTGAGGCAGAAGGCATCTCAGTTGTCNAAACTGGTTAATAAAAGTCAGAACATACCGACTAGAGTTGGCAGGGTTCTGGACTGTATGTTTAACTGCAAACTATGTGTTGAAATATCAGCTGACACTCTAATTCTGCGACCAGAATCTAAAGAAAGAATTGG', - M: 'GTGGATTGAGCATCTTAATTGCAGCATACTTGTCAACATCATGCATATATCATTGATGTATGCAGTTTTCTGCTTGCAGCTGTGCGGTCTAGGGAAAACTAACGGACTACACAATGGGACTGAACACAATAAGACACACGTTATGACAACGCCTGATGACAGTCAGAGCCCTGAACCGCCAGTGAGCACAGCCCTGCCTGTCACACCGGACCCTTCCACTGTCACACCTACAACACCAGCCAGCGGATTAGAAGGCTCAGGAGAGGTTCACACATCCTCTCCAATCACCACCAAGGGTTTGTCTCTGCCGGGGGCTACATCTGAGCTCCCTGCGACTACTAGCATAGTCACTTCAGGTGCAAGTGATGCCGATTCTAGCACACAGGCAGCCAGAGACACCCCTAAACCATCAGTCCGCACGAGTCTGCCCAACAGCCCTAGCACACCATCCACACCACAAGGCACACACCATCCCGTGAGGAGTCTGCTTTCAGTCACGAGCCCTAAGCCAGAAGAAACACCAACACCGTCAAAATCAAGCAAAGATAGCTCAGCAACCAACAGTCCTCACCCAGCCGCCAGCAGACCAACAACCCCTCCCACAACAGCCCAGAGACCCGCTGAAAACAACAGCCACAACACCACCGAACAGCTTGAGTCCTTAACACAATTAGCAACTTCAGGTTCAATGATCTCTCCAACACAGACAGTCCTCCCAAAGAGTGTTACTTCTATAGCCATTCAAGACATTCATCCCAGCCCAACAAATAGGTCTAAAAGAAACCTTGATATGGAAATAATCT', - S: 'GTGTTCTCTTGAGTGTTGGCAAAATGGAAAACAAAATCGAGGTGAACAACAAAGATGAGATGAACAAATGGTTTGAGGAGTTCAAGAAAGGAAATGGACTTGTGGACACTTTCACAAACTCNTATTCCTTTTGTGAAAGCGTNCCAAATCTGGACAGNTTTGTNTTCCAGATGGCNAGTGCCACTGATGATGCACAAAANGANTCCATCTACGCATCTGCNCTGGTGGANGCAACCAAATTTTGTGCACCTATATACGAGTGTGCTTGGGCTAGCTCCACTGGCATTGTTAAAAAGGGACTGGAGTGGTTCGAGAAAAATGCAGGAACCATTAAATCCTGGGATGAGAGTTATACTGAGCTTAAAGTTGAAGTTCCCAAAATAGAACAACTCTCCAACTACCAGCAGGCTGCTCTCAAATGGAGAAAAGACATAGGCTTCCGTGTCAATGCAAATACGGCAGCTTTGAGTAACAAAGTCCTAGCAGAGTACAAAGTTCCTGGCGAGATTGTAATGTCTGTCAAAGAGATGTTGTCAGATATGATTAGAAGNAGGAACCTGATTCTCAACAGAGGTGGTGATGAGAACCCACGCGGCCCAGTTAGCCGTGAACATGTGGAGTGGTGC', + [fastaHeaderL]: + 'CCACATTGACACAGANAGCTCCAGTAGTGGTTCTCTGTCCTTATTAAACCATGGACTTCTTAAGAAACCTTGACTGGACTCAGGTGATTGCTAGTCAGTATGTGACCAATCCCAGGTTTAATATCTCTGATTACTTCGAGATTGTTCGACAGCCTGGTGACGGGAACTGTTTCTACCACAGTATAGCTGAGTTAACCATGCCCAACAAAACAGATCACTCATACCATAACATCAAACATCTGACTGAGGTGGCAGCACGGAAGTATTATCAGGAGGAGCCGGAGGCTAAGCTCATTGGCCTGAGTCTGGAAGACTATCTTAAGAGGATGCTATCTGACAACGAATGGGGATCGACTCTTGAGGCATCTATGTTGGCTAAGGAAATGGGTATTACTATCATCATTTGGACTGTTGCAGCCAGTGACGAAGTGGAAGCAGGCATAAAGTTTGGTGATGGTGATGTGTTTACAGCCGTGAATCTTCTGCACTCCGGACAGACACACTTTGATGCCCTCAGAATACTGCCNCANTTTGAGGCTGACACAAGAGAGNCCTTNAGTCTGGTAGACAANNTNATAGCTGTGGACCANNTGACCTCNTCTTCAAGTGATGAANTGCAGGACTANGAAGANCTTGCTTTAGCACTTACNAGNGCGGAAGAACCATNTAGACGGTCTAGCNTGGATGAGGTNACCCTNTCTAAGAAACAAGCAGAGNTATTGAGGCAGAAGGCATCTCAGTTGTCNAAACTGGTTAATAAAAGTCAGAACATACCGACTAGAGTTGGCAGGGTTCTGGACTGTATGTTTAACTGCAAACTATGTGTTGAAATATCAGCTGACACTCTAATTCTGCGACCAGAATCTAAAGAAAGAATTGG', + [fastaHeaderM]: + 'GTGGATTGAGCATCTTAATTGCAGCATACTTGTCAACATCATGCATATATCATTGATGTATGCAGTTTTCTGCTTGCAGCTGTGCGGTCTAGGGAAAACTAACGGACTACACAATGGGACTGAACACAATAAGACACACGTTATGACAACGCCTGATGACAGTCAGAGCCCTGAACCGCCAGTGAGCACAGCCCTGCCTGTCACACCGGACCCTTCCACTGTCACACCTACAACACCAGCCAGCGGATTAGAAGGCTCAGGAGAGGTTCACACATCCTCTCCAATCACCACCAAGGGTTTGTCTCTGCCGGGGGCTACATCTGAGCTCCCTGCGACTACTAGCATAGTCACTTCAGGTGCAAGTGATGCCGATTCTAGCACACAGGCAGCCAGAGACACCCCTAAACCATCAGTCCGCACGAGTCTGCCCAACAGCCCTAGCACACCATCCACACCACAAGGCACACACCATCCCGTGAGGAGTCTGCTTTCAGTCACGAGCCCTAAGCCAGAAGAAACACCAACACCGTCAAAATCAAGCAAAGATAGCTCAGCAACCAACAGTCCTCACCCAGCCGCCAGCAGACCAACAACCCCTCCCACAACAGCCCAGAGACCCGCTGAAAACAACAGCCACAACACCACCGAACAGCTTGAGTCCTTAACACAATTAGCAACTTCAGGTTCAATGATCTCTCCAACACAGACAGTCCTCCCAAAGAGTGTTACTTCTATAGCCATTCAAGACATTCATCCCAGCCCAACAAATAGGTCTAAAAGAAACCTTGATATGGAAATAATCT', + [fastaHeaderS]: + 'GTGTTCTCTTGAGTGTTGGCAAAATGGAAAACAAAATCGAGGTGAACAACAAAGATGAGATGAACAAATGGTTTGAGGAGTTCAAGAAAGGAAATGGACTTGTGGACACTTTCACAAACTCNTATTCCTTTTGTGAAAGCGTNCCAAATCTGGACAGNTTTGTNTTCCAGATGGCNAGTGCCACTGATGATGCACAAAANGANTCCATCTACGCATCTGCNCTGGTGGANGCAACCAAATTTTGTGCACCTATATACGAGTGTGCTTGGGCTAGCTCCACTGGCATTGTTAAAAAGGGACTGGAGTGGTTCGAGAAAAATGCAGGAACCATTAAATCCTGGGATGAGAGTTATACTGAGCTTAAAGTTGAAGTTCCCAAAATAGAACAACTCTCCAACTACCAGCAGGCTGCTCTCAAATGGAGAAAAGACATAGGCTTCCGTGTCAATGCAAATACGGCAGCTTTGAGTAACAAAGTCCTAGCAGAGTACAAAGTTCCTGGCGAGATTGTAATGTCTGTCAAAGAGATGTTGTCAGATATGATTAGAAGNAGGAACCTGATTCTCAACAGAGGTGGTGATGAGAACCCACGCGGCCCAGTTAGCCGTGAACATGTGGAGTGGTGC', }); await submissionPage.acceptTerms(); diff --git a/integration-tests/tests/readonly.setup.ts b/integration-tests/tests/readonly.setup.ts index 0a1212c26a..dfef66b3bb 100644 --- a/integration-tests/tests/readonly.setup.ts +++ b/integration-tests/tests/readonly.setup.ts @@ -29,53 +29,33 @@ setup('Initialize some ebola sequences as base data', async ({ page }) => { } const submissionPage = new SingleSequenceSubmissionPage(page); - const mainSequence = - 'nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn' + - 'ATGGATAAACGGGTGAGAGGTTCATGGGCCCTGGGAGGACAATCTGAAGTTGATCTTGACTACCACAAAA' + - 'TATTAACAGCCGGGCTTTCGGTCCAACAAGGGATTGTGCGACAAAGAGTCATCCCGGTATATGTTGTGAG' + - 'TGATCTTGAGGGTATTTGTCAACATATCATTCAGGCCTTTGAAGCAGGCGTAGATTTCCAAGATAATGCT' + - 'GACAGCTTCCTTTTACTTTTATGTTTACATCATGCTTACCAAGGAGATCATAGGCTCTTCCTCAAAAGTG' + - 'ATGCAGTTCAATACTTAGAGGGCCATGGTTTCAGGTTTGAGGTCCGAGAAAAGGAGAATGTGCACCGTCT' + - 'GGATGAATTGTTGCCCAATGTCACCGGTGGAAAAAATCTTAGGAGAACATTGGCTGCAATGCCTGAAGAG' + - 'GAGACAACAGAAGCTAATGCTGGTCAGTTTTTATCCTTTGCCAGTTTGTTTCTACCCAAACTTGTCGTTG' + - 'GGGAGAAAGCGTGTCTGGAAAAAGTACAAAGGCAGATTCAGGTCCATGCAGAACAAGGGCTCATTCAATA' + - 'TCCAACTTCCTGGCAATCAGTTGGACACATGATGGTGATCTTCCGTTTGATGAGAACAAACTTTTTAATC' + - 'AAGTTCCTACTAATACATCAGGGGATGCACATGGTCGCAGGCCATGATGCGAATGACACAGTAATATCTA' + - 'ATTCTGTTGCCCAAGCAAGGTTCTCTGGTCTTCTGATTGTAAAGACTGTTCTGGACCACATCCTACAAAA' + - 'AACAGATCTTGGAGTACGACTTCATCCACTGGCCAGGACAGCAAAAGTCAAGAATGAGGTCAGTTCATTC' + - 'AAGGCAGCTCTTGGCTCACTTGCCAAGCATGGAGAATATGCTCCATTTGCACGTCTCCTCAATCTTTCTG'; - - const sequences = [ + const submissionId = 'foobar-readonly'; + const reviewPage = await submissionPage.completeSubmission( { - submissionId: 'foobar-readonly-1', + submissionId: submissionId, collectionCountry: 'France', collectionDate: '2021-05-12', authorAffiliations: 'Patho Institute, Paris', + groupId: groupId.toString(), }, { - submissionId: 'foobar-readonly-2', - collectionCountry: 'Brazil', - collectionDate: '2021-06-15', - authorAffiliations: 'Research Center, Rio', - }, - { - submissionId: 'foobar-readonly-3', - collectionCountry: 'Switzerland', - collectionDate: '2021-07-20', - authorAffiliations: 'University Hospital, Zurich', + [submissionId]: + 'nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn' + + 'ATGGATAAACGGGTGAGAGGTTCATGGGCCCTGGGAGGACAATCTGAAGTTGATCTTGACTACCACAAAA' + + 'TATTAACAGCCGGGCTTTCGGTCCAACAAGGGATTGTGCGACAAAGAGTCATCCCGGTATATGTTGTGAG' + + 'TGATCTTGAGGGTATTTGTCAACATATCATTCAGGCCTTTGAAGCAGGCGTAGATTTCCAAGATAATGCT' + + 'GACAGCTTCCTTTTACTTTTATGTTTACATCATGCTTACCAAGGAGATCATAGGCTCTTCCTCAAAAGTG' + + 'ATGCAGTTCAATACTTAGAGGGCCATGGTTTCAGGTTTGAGGTCCGAGAAAAGGAGAATGTGCACCGTCT' + + 'GGATGAATTGTTGCCCAATGTCACCGGTGGAAAAAATCTTAGGAGAACATTGGCTGCAATGCCTGAAGAG' + + 'GAGACAACAGAAGCTAATGCTGGTCAGTTTTTATCCTTTGCCAGTTTGTTTCTACCCAAACTTGTCGTTG' + + 'GGGAGAAAGCGTGTCTGGAAAAAGTACAAAGGCAGATTCAGGTCCATGCAGAACAAGGGCTCATTCAATA' + + 'TCCAACTTCCTGGCAATCAGTTGGACACATGATGGTGATCTTCCGTTTGATGAGAACAAACTTTTTAATC' + + 'AAGTTCCTACTAATACATCAGGGGATGCACATGGTCGCAGGCCATGATGCGAATGACACAGTAATATCTA' + + 'ATTCTGTTGCCCAAGCAAGGTTCTCTGGTCTTCTGATTGTAAAGACTGTTCTGGACCACATCCTACAAAA' + + 'AACAGATCTTGGAGTACGACTTCATCCACTGGCCAGGACAGCAAAAGTCAAGAATGAGGTCAGTTCATTC' + + 'AAGGCAGCTCTTGGCTCACTTGCCAAGCATGGAGAATATGCTCCATTTGCACGTCTCCTCAATCTTTCTG', }, - ]; - - for (const seq of sequences) { - const reviewPage = await submissionPage.completeSubmission( - { - ...seq, - groupId: groupId.toString(), - }, - { - main: mainSequence, - }, - ); + ); await reviewPage.waitForZeroProcessing(); await reviewPage.releaseValidSequences(); diff --git a/integration-tests/tests/specs/features/revise-sequence.spec.ts b/integration-tests/tests/specs/features/revise-sequence.spec.ts index 3838442ce5..e339028018 100644 --- a/integration-tests/tests/specs/features/revise-sequence.spec.ts +++ b/integration-tests/tests/specs/features/revise-sequence.spec.ts @@ -41,11 +41,11 @@ sequenceTest( await page.getByTestId('discard_L_segment_file').click(); await page.getByTestId('discard_S_segment_file').click(); - await page.getByTestId('Add a segment_segment_file').setInputFiles({ - name: 'update_S.txt', - mimeType: 'text/plain', - buffer: Buffer.from('>S\nAAAAA'), - }); + // await page.getByTestId('Add a segment_segment_file').setInputFiles({ + // name: 'update_S.txt', + // mimeType: 'text/plain', + // buffer: Buffer.from('>S\nAAAAA'), + // }); await page.getByRole('button', { name: 'Submit' }).click(); await page.getByRole('button', { name: 'Confirm' }).click(); @@ -54,13 +54,13 @@ sequenceTest( await reviewPage.waitForZeroProcessing(); await reviewPage.viewSequences(); - const tabs = await reviewPage.getAvailableSequenceTabs(); - expect(tabs).not.toContain('L (aligned)'); - expect(tabs).not.toContain('L (unaligned)'); - expect(tabs).toContain('S (unaligned)'); + const tabs = await reviewPage.getAvailableSequenceTabs(); + expect(tabs).not.toContain('L (aligned)'); + expect(tabs).not.toContain('L (unaligned)'); - await reviewPage.switchSequenceTab('S (unaligned)'); - expect(await reviewPage.getSequenceContent()).toBe('AAAAA'); + // expect(tabs).toContain('S (unaligned)'); + // await reviewPage.switchSequenceTab('S (unaligned)'); + // expect(await reviewPage.getSequenceContent()).toBe('AAAAA'); await reviewPage.closeSequencesDialog(); }, diff --git a/integration-tests/tests/specs/features/search/override-hidden-fields.spec.ts b/integration-tests/tests/specs/features/search/override-hidden-fields.spec.ts index 909c95a474..28e9947752 100644 --- a/integration-tests/tests/specs/features/search/override-hidden-fields.spec.ts +++ b/integration-tests/tests/specs/features/search/override-hidden-fields.spec.ts @@ -21,7 +21,7 @@ test('Override hidden fields', async ({ page, groupId }) => { authorAffiliations: uuid, }, { - main: 'ATTGATCTCATCATTTACCAATTGGAGACCGTTTAACTAGTCAATCCCCCATTTGGGGGCATTCCTAAAGTGTTGCAAAGGTATGTGGGTCGTATTGCTTTGCCTTTTCCTAACCTGGCTCCTCCTACAATTCTAACCTGCTTGATAAGTGTGATTACCTGAGTAATAGACTAATTTCGTCCTGGTAATTAGCATTTTCTAGTAAAACCAATACTATCTCAAGTCCTAAGAGGAGGTGAGAAGAGGGTCTCGAGGTATCCCTCCAGTCCACAAAATCTAGCTAATTTTAGCTGAGTGGACTGATTACTCTCATCACACGCTAACTACTAAGGGTTTACCTGAGAGCCTACAACATGGATAAACGGGTGAGAGGTTCATGGGCCCTGGGAGGACAATCTGAAGTTGATCTTGACTACCACAAAATATTAACAGCCGGGCTTTCGGTCCAACAAGGGATTGTGCGACAAAGAGTCATCCCGGTATATGTTGTGAGTGATCTTGAGGGTATTTGTCAACATATCATTCAGGCCTTTGAAGCAGGCGTAGATTTCCAAGATAATGCTGACAGCTTCCTTTTACTTTTATGTTTACATCATGCTTACCAAGGAGATCATAGGCTCTTCCTCAAAAGTGATGCAGTTCAATACTTAGAGGGCCATGGTTTCAGGTTTGAGGTCCGAGAAAAGGAGAATGTGCACCGTCTGGATGAATTGTTGCCCAATGTCACCGGTGGAAAAAATCTTAGGAGAACATTGGCTGCAATGCCTGAAGAGGAGACAACAGAAGCTAACGCTGGTCAGTTTTTATCCTTTGCCAGTTTGTTTCTACCCAAACTTGTCGTTGGGGAGAAAGCGTGTCTGGAAAAAGTACAAAGGCAGATTCAGGTCCATGCAGAACAAGGGCTCATTCAATATCCAACTTCCTGGCAATCAGTTGGACACATGATGGTGATCTTCCGTTTGATGAGAACAAACTTTTTAATCAAGTTCCTACTAATACATCAGGGGATGCACATGG', + foo1: 'ATTGATCTCATCATTTACCAATTGGAGACCGTTTAACTAGTCAATCCCCCATTTGGGGGCATTCCTAAAGTGTTGCAAAGGTATGTGGGTCGTATTGCTTTGCCTTTTCCTAACCTGGCTCCTCCTACAATTCTAACCTGCTTGATAAGTGTGATTACCTGAGTAATAGACTAATTTCGTCCTGGTAATTAGCATTTTCTAGTAAAACCAATACTATCTCAAGTCCTAAGAGGAGGTGAGAAGAGGGTCTCGAGGTATCCCTCCAGTCCACAAAATCTAGCTAATTTTAGCTGAGTGGACTGATTACTCTCATCACACGCTAACTACTAAGGGTTTACCTGAGAGCCTACAACATGGATAAACGGGTGAGAGGTTCATGGGCCCTGGGAGGACAATCTGAAGTTGATCTTGACTACCACAAAATATTAACAGCCGGGCTTTCGGTCCAACAAGGGATTGTGCGACAAAGAGTCATCCCGGTATATGTTGTGAGTGATCTTGAGGGTATTTGTCAACATATCATTCAGGCCTTTGAAGCAGGCGTAGATTTCCAAGATAATGCTGACAGCTTCCTTTTACTTTTATGTTTACATCATGCTTACCAAGGAGATCATAGGCTCTTCCTCAAAAGTGATGCAGTTCAATACTTAGAGGGCCATGGTTTCAGGTTTGAGGTCCGAGAAAAGGAGAATGTGCACCGTCTGGATGAATTGTTGCCCAATGTCACCGGTGGAAAAAATCTTAGGAGAACATTGGCTGCAATGCCTGAAGAGGAGACAACAGAAGCTAACGCTGGTCAGTTTTTATCCTTTGCCAGTTTGTTTCTACCCAAACTTGTCGTTGGGGAGAAAGCGTGTCTGGAAAAAGTACAAAGGCAGATTCAGGTCCATGCAGAACAAGGGCTCATTCAATATCCAACTTCCTGGCAATCAGTTGGACACATGATGGTGATCTTCCGTTTGATGAGAACAAACTTTTTAATCAAGTTCCTACTAATACATCAGGGGATGCACATGG', }, ); await page.goto('/'); @@ -33,7 +33,7 @@ test('Override hidden fields', async ({ page, groupId }) => { authorAffiliations: uuid, }, { - main: 'ATTGATCTCATCATTTACCAATTGGAGACCGTTTAACTAGTCAATCCCCCATTTGGGGGCATTCCTAAAGTGTTGCAAAGGTATGTGGGTCGTATTGCTTTGCCTTTTCCTAACCTGGCTCCTCCTACAATTCTAACCTGCTTGATAAGTGTGATTACCTGAGTAATAGACTAATTTCGTCCTGGTAATTAGCATTTTCTAGTAAAACCAATACTATCTCAAGTCCTAAGAGGAGGTGAGAAGAGGGTCTCGAGGTATCCCTCCAGTCCACAAAATCTAGCTAATTTTAGCTGAGTGGACTGATTACTCTCATCACACGCTAACTACTAAGGGTTTACCTGAGAGCCTACAACATGGATAAACGGGTGAGAGGTTCATGGGCCCTGGGAGGACAATCTGAAGTTGATCTTGACTACCACAAAATATTAACAGCCGGGCTTTCGGTCCAACAAGGGATTGTGCGACAAAGAGTCATCCCGGTATATGTTGTGAGTGATCTTGAGGGTATTTGTCAACATATCATTCAGGCCTTTGAAGCAGGCGTAGATTTCCAAGATAATGCTGACAGCTTCCTTTTACTTTTATGTTTACATCATGCTTACCAAGGAGATCATAGGCTCTTCCTCAAAAGTGATGCAGTTCAATACTTAGAGGGCCATGGTTTCAGGTTTGAGGTCCGAGAAAAGGAGAATGTGCACCGTCTGGATGAATTGTTGCCCAATGTCACCGGTGGAAAAAATCTTAGGAGAACATTGGCTGCAATGCCTGAAGAGGAGACAACAGAAGCTAACGCTGGTCAGTTTTTATCCTTTGCCAGTTTGTTTCTACCCAAACTTGTCGTTGGGGAGAAAGCGTGTCTGGAAAAAGTACAAAGGCAGATTCAGGTCCATGCAGAACAAGGGCTCATTCAATATCCAACTTCCTGGCAATCAGTTGGACACATGATGGTGATCTTCCGTTTGATGAGAACAAACTTTTTAATCAAGTTCCTACTAATACATCAGGGGATGCACATGG', + foo1: 'ATTGATCTCATCATTTACCAATTGGAGACCGTTTAACTAGTCAATCCCCCATTTGGGGGCATTCCTAAAGTGTTGCAAAGGTATGTGGGTCGTATTGCTTTGCCTTTTCCTAACCTGGCTCCTCCTACAATTCTAACCTGCTTGATAAGTGTGATTACCTGAGTAATAGACTAATTTCGTCCTGGTAATTAGCATTTTCTAGTAAAACCAATACTATCTCAAGTCCTAAGAGGAGGTGAGAAGAGGGTCTCGAGGTATCCCTCCAGTCCACAAAATCTAGCTAATTTTAGCTGAGTGGACTGATTACTCTCATCACACGCTAACTACTAAGGGTTTACCTGAGAGCCTACAACATGGATAAACGGGTGAGAGGTTCATGGGCCCTGGGAGGACAATCTGAAGTTGATCTTGACTACCACAAAATATTAACAGCCGGGCTTTCGGTCCAACAAGGGATTGTGCGACAAAGAGTCATCCCGGTATATGTTGTGAGTGATCTTGAGGGTATTTGTCAACATATCATTCAGGCCTTTGAAGCAGGCGTAGATTTCCAAGATAATGCTGACAGCTTCCTTTTACTTTTATGTTTACATCATGCTTACCAAGGAGATCATAGGCTCTTCCTCAAAAGTGATGCAGTTCAATACTTAGAGGGCCATGGTTTCAGGTTTGAGGTCCGAGAAAAGGAGAATGTGCACCGTCTGGATGAATTGTTGCCCAATGTCACCGGTGGAAAAAATCTTAGGAGAACATTGGCTGCAATGCCTGAAGAGGAGACAACAGAAGCTAACGCTGGTCAGTTTTTATCCTTTGCCAGTTTGTTTCTACCCAAACTTGTCGTTGGGGAGAAAGCGTGTCTGGAAAAAGTACAAAGGCAGATTCAGGTCCATGCAGAACAAGGGCTCATTCAATATCCAACTTCCTGGCAATCAGTTGGACACATGATGGTGATCTTCCGTTTGATGAGAACAAACTTTTTAATCAAGTTCCTACTAATACATCAGGGGATGCACATGG', }, ); diff --git a/integration-tests/tests/specs/features/sequence-view.dependent.spec.ts b/integration-tests/tests/specs/features/sequence-view.dependent.spec.ts index b891f8c394..c05cc17a8c 100644 --- a/integration-tests/tests/specs/features/sequence-view.dependent.spec.ts +++ b/integration-tests/tests/specs/features/sequence-view.dependent.spec.ts @@ -11,18 +11,26 @@ test.describe('Sequence view in review card', () => { test.setTimeout(120000); void groupId; const submissionPage = new SingleSequenceSubmissionPage(page); + const submissionId = 'TEST_SEQ_VIEW'; await submissionPage.navigateToSubmissionPage('Crimean-Congo Hemorrhagic Fever Virus'); await submissionPage.fillSubmissionForm({ - submissionId: 'TEST_SEQ_VIEW', + submissionId: submissionId, collectionCountry: 'Brazil', collectionDate: '2023-01-15', authorAffiliations: 'Test Lab, University of Testing', }); + const fastaHeaderL = `${submissionId}_L`; + const fastaHeaderM = `${submissionId}_M`; + const fastaHeaderS = `${submissionId}_S`; + await submissionPage.fillSequenceData({ - L: 'CCACATTGACACAGANAGCTCCAGTAGTGGTTCTCTGTCCTTATTAAACCATGGACTTCTTAAGAAACCTTGACTGGACTCAGGTGATTGCTAGTCAGTATGTGACCAATCCC', - M: 'GTGGATTGAGCATCTTAATTGCAGCATACTTGTCAACATCATGCATATATCATTGATGTATGCAGTTTTCTGCTTGCAGCTGTGCGGTCTAGGGAAAACTAACGGACTACACA', - S: 'GTGTTCTCTTGAGTGTTGGCAAAATGGAAAACAAAATCGAGGTGAACAACAAAGATGAGATGAACAAATGGTTTGAGGAGTTCAAGAAAGGAAATGGACTTGTGGACACTTTC', + [fastaHeaderL]: + 'CCACATTGACACAGANAGCTCCAGTAGTGGTTCTCTGTCCTTATTAAACCATGGACTTCTTAAGAAACCTTGACTGGACTCAGGTGATTGCTAGTCAGTATGTGACCAATCCC', + [fastaHeaderM]: + 'GTGGATTGAGCATCTTAATTGCAGCATACTTGTCAACATCATGCATATATCATTGATGTATGCAGTTTTCTGCTTGCAGCTGTGCGGTCTAGGGAAAACTAACGGACTACACA', + [fastaHeaderS]: + 'GTGTTCTCTTGAGTGTTGGCAAAATGGAAAACAAAATCGAGGTGAACAACAAAGATGAGATGAACAAATGGTTTGAGGAGTTCAAGAAAGGAAATGGACTTGTGGACACTTTC', }); await submissionPage.acceptTerms(); await submissionPage.submitSequence(); diff --git a/integration-tests/tests/specs/features/single-submit.spec.ts b/integration-tests/tests/specs/features/single-submit.spec.ts index ef9aa5b78f..99b1ee2b25 100644 --- a/integration-tests/tests/specs/features/single-submit.spec.ts +++ b/integration-tests/tests/specs/features/single-submit.spec.ts @@ -5,16 +5,18 @@ test('submit a single sequence', async ({ page, groupId }) => { test.setTimeout(90_000); void groupId; const submissionPage = new SingleSequenceSubmissionPage(page); + const submissionId = 'TEST-ID-123'; await submissionPage.navigateToSubmissionPage(); await submissionPage.fillSubmissionForm({ - submissionId: 'TEST-ID-123', + submissionId: submissionId, collectionCountry: 'Uganda', collectionDate: '2023-10-15', authorAffiliations: 'Research Lab, University', }); await submissionPage.fillSequenceData({ - main: 'ATTGATCTCATCATTTACCAATTGGAGACCGTTTAACTAGTCAATCCCCCATTTGGGGGCATTCCTAAAGTGTTGCAAAGGTATGTGGGTCGTATTGCTTTGCCTTTTCCTAACCTGGCTCCTCCTACAATTCTAACCTGCTTGATAAGTGTGATTACCTGAGTAATAGACTAATTTCGTCCTGGTAATTAGCATTTTCTAGTAAAACCAATACTATCTCAAGTCCTAAGAGGAGGTGAGAAGAGGGTCTCGAGGTATCCCTCCAGTCCACAAAATCTAGCTAATTTTAGCTGAGTGGACTGATTACTCTCATCACACGCTAACTACTAAGGGTTTACCTGAGAGCCTACAACATGGATAAACGGGTGAGAGGTTCATGGGCCCTGGGAGGACAATCTGAAGTTGATCTTGACTACCACAAAATATTAACAGCCGGGCTTTCGGTCCAACAAGGGATTGTGCGACAAAGAGTCATCCCGGTATATGTTGTGAGTGATCTTGAGGGTATTTGTCAACATATCATTCAGGCCTTTGAAGCAGGCGTAGATTTCCAAGATAATGCTGACAGCTTCCTTTTACTTTTATGTTTACATCATGCTTACCAAGGAGATCATAGGCTCTTCCTCAAAAGTGATGCAGTTCAATACTTAGAGGGCCATGGTTTCAGGTTTGAGGTCCGAGAAAAGGAGAATGTGCACCGTCTGGATGAATTGTTGCCCAATGTCACCGGTGGAAAAAATCTTAGGAGAACATTGGCTGCAATGCCTGAAGAGGAGACAACAGAAGCTAACGCTGGTCAGTTTTTATCCTTTGCCAGTTTGTTTCTACCCAAACTTGTCGTTGGGGAGAAAGCGTGTCTGGAAAAAGTACAAAGGCAGATTCAGGTCCATGCAGAACAAGGGCTCATTCAATATCCAACTTCCTGGCAATCAGTTGGACACATGATGGTGATCTTCCGTTTGATGAGAACAAACTTTTTAATCAAGTTCCTACTAATACATCAGGGGATGCACATGG', + [submissionId]: + 'ATTGATCTCATCATTTACCAATTGGAGACCGTTTAACTAGTCAATCCCCCATTTGGGGGCATTCCTAAAGTGTTGCAAAGGTATGTGGGTCGTATTGCTTTGCCTTTTCCTAACCTGGCTCCTCCTACAATTCTAACCTGCTTGATAAGTGTGATTACCTGAGTAATAGACTAATTTCGTCCTGGTAATTAGCATTTTCTAGTAAAACCAATACTATCTCAAGTCCTAAGAGGAGGTGAGAAGAGGGTCTCGAGGTATCCCTCCAGTCCACAAAATCTAGCTAATTTTAGCTGAGTGGACTGATTACTCTCATCACACGCTAACTACTAAGGGTTTACCTGAGAGCCTACAACATGGATAAACGGGTGAGAGGTTCATGGGCCCTGGGAGGACAATCTGAAGTTGATCTTGACTACCACAAAATATTAACAGCCGGGCTTTCGGTCCAACAAGGGATTGTGCGACAAAGAGTCATCCCGGTATATGTTGTGAGTGATCTTGAGGGTATTTGTCAACATATCATTCAGGCCTTTGAAGCAGGCGTAGATTTCCAAGATAATGCTGACAGCTTCCTTTTACTTTTATGTTTACATCATGCTTACCAAGGAGATCATAGGCTCTTCCTCAAAAGTGATGCAGTTCAATACTTAGAGGGCCATGGTTTCAGGTTTGAGGTCCGAGAAAAGGAGAATGTGCACCGTCTGGATGAATTGTTGCCCAATGTCACCGGTGGAAAAAATCTTAGGAGAACATTGGCTGCAATGCCTGAAGAGGAGACAACAGAAGCTAACGCTGGTCAGTTTTTATCCTTTGCCAGTTTGTTTCTACCCAAACTTGTCGTTGGGGAGAAAGCGTGTCTGGAAAAAGTACAAAGGCAGATTCAGGTCCATGCAGAACAAGGGCTCATTCAATATCCAACTTCCTGGCAATCAGTTGGACACATGATGGTGATCTTCCGTTTGATGAGAACAAACTTTTTAATCAAGTTCCTACTAATACATCAGGGGATGCACATGG', }); await submissionPage.acceptTerms(); await submissionPage.submitSequence(); @@ -25,15 +27,17 @@ test('submit a single sequence, all in one method', async ({ page, groupId }) => test.setTimeout(90_000); void groupId; const submissionPage = new SingleSequenceSubmissionPage(page); + const submissionId = 'TEST-ID-123'; await submissionPage.completeSubmission( { - submissionId: 'TEST-ID-123', + submissionId: submissionId, collectionCountry: 'Uganda', collectionDate: '2023-10-15', authorAffiliations: 'Research Lab, University', }, { - main: 'ATTGATCTCATCATTTACCAATTGGAGACCGTTTAACTAGTCAATCCCCCATTTGGGGGCATTCCTAAAGTGTTGCAAAGGTATGTGGGTCGTATTGCTTTGCCTTTTCCTAACCTGGCTCCTCCTACAATTCTAACCTGCTTGATAAGTGTGATTACCTGAGTAATAGACTAATTTCGTCCTGGTAATTAGCATTTTCTAGTAAAACCAATACTATCTCAAGTCCTAAGAGGAGGTGAGAAGAGGGTCTCGAGGTATCCCTCCAGTCCACAAAATCTAGCTAATTTTAGCTGAGTGGACTGATTACTCTCATCACACGCTAACTACTAAGGGTTTACCTGAGAGCCTACAACATGGATAAACGGGTGAGAGGTTCATGGGCCCTGGGAGGACAATCTGAAGTTGATCTTGACTACCACAAAATATTAACAGCCGGGCTTTCGGTCCAACAAGGGATTGTGCGACAAAGAGTCATCCCGGTATATGTTGTGAGTGATCTTGAGGGTATTTGTCAACATATCATTCAGGCCTTTGAAGCAGGCGTAGATTTCCAAGATAATGCTGACAGCTTCCTTTTACTTTTATGTTTACATCATGCTTACCAAGGAGATCATAGGCTCTTCCTCAAAAGTGATGCAGTTCAATACTTAGAGGGCCATGGTTTCAGGTTTGAGGTCCGAGAAAAGGAGAATGTGCACCGTCTGGATGAATTGTTGCCCAATGTCACCGGTGGAAAAAATCTTAGGAGAACATTGGCTGCAATGCCTGAAGAGGAGACAACAGAAGCTAACGCTGGTCAGTTTTTATCCTTTGCCAGTTTGTTTCTACCCAAACTTGTCGTTGGGGAGAAAGCGTGTCTGGAAAAAGTACAAAGGCAGATTCAGGTCCATGCAGAACAAGGGCTCATTCAATATCCAACTTCCTGGCAATCAGTTGGACACATGATGGTGATCTTCCGTTTGATGAGAACAAACTTTTTAATCAAGTTCCTACTAATACATCAGGGGATGCACATGG', + [submissionId]: + 'ATTGATCTCATCATTTACCAATTGGAGACCGTTTAACTAGTCAATCCCCCATTTGGGGGCATTCCTAAAGTGTTGCAAAGGTATGTGGGTCGTATTGCTTTGCCTTTTCCTAACCTGGCTCCTCCTACAATTCTAACCTGCTTGATAAGTGTGATTACCTGAGTAATAGACTAATTTCGTCCTGGTAATTAGCATTTTCTAGTAAAACCAATACTATCTCAAGTCCTAAGAGGAGGTGAGAAGAGGGTCTCGAGGTATCCCTCCAGTCCACAAAATCTAGCTAATTTTAGCTGAGTGGACTGATTACTCTCATCACACGCTAACTACTAAGGGTTTACCTGAGAGCCTACAACATGGATAAACGGGTGAGAGGTTCATGGGCCCTGGGAGGACAATCTGAAGTTGATCTTGACTACCACAAAATATTAACAGCCGGGCTTTCGGTCCAACAAGGGATTGTGCGACAAAGAGTCATCCCGGTATATGTTGTGAGTGATCTTGAGGGTATTTGTCAACATATCATTCAGGCCTTTGAAGCAGGCGTAGATTTCCAAGATAATGCTGACAGCTTCCTTTTACTTTTATGTTTACATCATGCTTACCAAGGAGATCATAGGCTCTTCCTCAAAAGTGATGCAGTTCAATACTTAGAGGGCCATGGTTTCAGGTTTGAGGTCCGAGAAAAGGAGAATGTGCACCGTCTGGATGAATTGTTGCCCAATGTCACCGGTGGAAAAAATCTTAGGAGAACATTGGCTGCAATGCCTGAAGAGGAGACAACAGAAGCTAACGCTGGTCAGTTTTTATCCTTTGCCAGTTTGTTTCTACCCAAACTTGTCGTTGGGGAGAAAGCGTGTCTGGAAAAAGTACAAAGGCAGATTCAGGTCCATGCAGAACAAGGGCTCATTCAATATCCAACTTCCTGGCAATCAGTTGGACACATGATGGTGATCTTCCGTTTGATGAGAACAAACTTTTTAATCAAGTTCCTACTAATACATCAGGGGATGCACATGG', }, ); await page.waitForURL('**/review'); diff --git a/integration-tests/tests/specs/features/submission-flow.spec.ts b/integration-tests/tests/specs/features/submission-flow.spec.ts index b3443d5624..1825faa22b 100644 --- a/integration-tests/tests/specs/features/submission-flow.spec.ts +++ b/integration-tests/tests/specs/features/submission-flow.spec.ts @@ -67,18 +67,25 @@ test.describe('Submission flow', () => { test.setTimeout(120_000); void groupId; const submissionPage = new SingleSequenceSubmissionPage(page); + const submissionId = 'XF499'; await submissionPage.navigateToSubmissionPage('Crimean-Congo Hemorrhagic Fever Virus'); await submissionPage.fillSubmissionForm({ - submissionId: 'XF499', + submissionId: submissionId, collectionCountry: 'Colombia', collectionDate: '2021-12-12', authorAffiliations: 'Research Lab, University of Example', }); + const fastaHeaderL = `${submissionId}_L`; + const fastaHeaderM = `${submissionId}_M`; + const fastaHeaderS = `${submissionId}_S`; await submissionPage.fillSequenceData({ - L: 'CCACATTGACACAGANAGCTCCAGTAGTGGTTCTCTGTCCTTATTAAACCATGGACTTCTTAAGAAACCTTGACTGGACTCAGGTGATTGCTAGTCAGTATGTGACCAATCCCAGGTTTAATATCTCTGATTACTTCGAGATTGTTCGACAGCCTGGTGACGGGAACTGTTTCTACCACAGTATAGCTGAGTTAACCATGCCCAACAAAACAGATCACTCATACCATAACATCAAACATCTGACTGAGGTGGCAGCACGGAAGTATTATCAGGAGGAGCCGGAGGCTAAGCTCATTGGCCTGAGTCTGGAAGACTATCTTAAGAGGATGCTATCTGACAACGAATGGGGATCGACTCTTGAGGCATCTATGTTGGCTAAGGAAATGGGTATTACTATCATCATTTGGACTGTTGCAGCCAGTGACGAAGTGGAAGCAGGCATAAAGTTTGGTGATGGTGATGTGTTTACAGCCGTGAATCTTCTGCACTCCGGACAGACACACTTTGATGCCCTCAGAATACTGCCNCANTTTGAGGCTGACACAAGAGAGNCCTTNAGTCTGGTAGACAANNTNATAGCTGTGGACCANNTGACCTCNTCTTCAAGTGATGAANTGCAGGACTANGAAGANCTTGCTTTAGCACTTACNAGNGCGGAAGAACCATNTAGACGGTCTAGCNTGGATGAGGTNACCCTNTCTAAGAAACAAGCAGAGNTATTGAGGCAGAAGGCATCTCAGTTGTCNAAACTGGTTAATAAAAGTCAGAACATACCGACTAGAGTTGGCAGGGTTCTGGACTGTATGTTTAACTGCAAACTATGTGTTGAAATATCAGCTGACACTCTAATTCTGCGACCAGAATCTAAAGAAAGAATTGG', - M: 'GTGGATTGAGCATCTTAATTGCAGCATACTTGTCAACATCATGCATATATCATTGATGTATGCAGTTTTCTGCTTGCAGCTGTGCGGTCTAGGGAAAACTAACGGACTACACAATGGGACTGAACACAATAAGACACACGTTATGACAACGCCTGATGACAGTCAGAGCCCTGAACCGCCAGTGAGCACAGCCCTGCCTGTCACACCGGACCCTTCCACTGTCACACCTACAACACCAGCCAGCGGATTAGAAGGCTCAGGAGAGGTTCACACATCCTCTCCAATCACCACCAAGGGTTTGTCTCTGCCGGGGGCTACATCTGAGCTCCCTGCGACTACTAGCATAGTCACTTCAGGTGCAAGTGATGCCGATTCTAGCACACAGGCAGCCAGAGACACCCCTAAACCATCAGTCCGCACGAGTCTGCCCAACAGCCCTAGCACACCATCCACACCACAAGGCACACACCATCCCGTGAGGAGTCTGCTTTCAGTCACGAGCCCTAAGCCAGAAGAAACACCAACACCGTCAAAATCAAGCAAAGATAGCTCAGCAACCAACAGTCCTCACCCAGCCGCCAGCAGACCAACAACCCCTCCCACAACAGCCCAGAGACCCGCTGAAAACAACAGCCACAACACCACCGAACAGCTTGAGTCCTTAACACAATTAGCAACTTCAGGTTCAATGATCTCTCCAACACAGACAGTCCTCCCAAAGAGTGTTACTTCTATAGCCATTCAAGACATTCATCCCAGCCCAACAAATAGGTCTAAAAGAAACCTTGATATGGAAATAATCT', - S: 'GTGTTCTCTTGAGTGTTGGCAAAATGGAAAACAAAATCGAGGTGAACAACAAAGATGAGATGAACAAATGGTTTGAGGAGTTCAAGAAAGGAAATGGACTTGTGGACACTTTCACAAACTCNTATTCCTTTTGTGAAAGCGTNCCAAATCTGGACAGNTTTGTNTTCCAGATGGCNAGTGCCACTGATGATGCACAAAANGANTCCATCTACGCATCTGCNCTGGTGGANGCAACCAAATTTTGTGCACCTATATACGAGTGTGCTTGGGCTAGCTCCACTGGCATTGTTAAAAAGGGACTGGAGTGGTTCGAGAAAAATGCAGGAACCATTAAATCCTGGGATGAGAGTTATACTGAGCTTAAAGTTGAAGTTCCCAAAATAGAACAACTCTCCAACTACCAGCAGGCTGCTCTCAAATGGAGAAAAGACATAGGCTTCCGTGTCAATGCAAATACGGCAGCTTTGAGTAACAAAGTCCTAGCAGAGTACAAAGTTCCTGGCGAGATTGTAATGTCTGTCAAAGAGATGTTGTCAGATATGATTAGAAGNAGGAACCTGATTCTCAACAGAGGTGGTGATGAGAACCCACGCGGCCCAGTTAGCCGTGAACATGTGGAGTGGTGC', + [fastaHeaderL]: + 'CCACATTGACACAGANAGCTCCAGTAGTGGTTCTCTGTCCTTATTAAACCATGGACTTCTTAAGAAACCTTGACTGGACTCAGGTGATTGCTAGTCAGTATGTGACCAATCCCAGGTTTAATATCTCTGATTACTTCGAGATTGTTCGACAGCCTGGTGACGGGAACTGTTTCTACCACAGTATAGCTGAGTTAACCATGCCCAACAAAACAGATCACTCATACCATAACATCAAACATCTGACTGAGGTGGCAGCACGGAAGTATTATCAGGAGGAGCCGGAGGCTAAGCTCATTGGCCTGAGTCTGGAAGACTATCTTAAGAGGATGCTATCTGACAACGAATGGGGATCGACTCTTGAGGCATCTATGTTGGCTAAGGAAATGGGTATTACTATCATCATTTGGACTGTTGCAGCCAGTGACGAAGTGGAAGCAGGCATAAAGTTTGGTGATGGTGATGTGTTTACAGCCGTGAATCTTCTGCACTCCGGACAGACACACTTTGATGCCCTCAGAATACTGCCNCANTTTGAGGCTGACACAAGAGAGNCCTTNAGTCTGGTAGACAANNTNATAGCTGTGGACCANNTGACCTCNTCTTCAAGTGATGAANTGCAGGACTANGAAGANCTTGCTTTAGCACTTACNAGNGCGGAAGAACCATNTAGACGGTCTAGCNTGGATGAGGTNACCCTNTCTAAGAAACAAGCAGAGNTATTGAGGCAGAAGGCATCTCAGTTGTCNAAACTGGTTAATAAAAGTCAGAACATACCGACTAGAGTTGGCAGGGTTCTGGACTGTATGTTTAACTGCAAACTATGTGTTGAAATATCAGCTGACACTCTAATTCTGCGACCAGAATCTAAAGAAAGAATTGG', + [fastaHeaderM]: + 'GTGGATTGAGCATCTTAATTGCAGCATACTTGTCAACATCATGCATATATCATTGATGTATGCAGTTTTCTGCTTGCAGCTGTGCGGTCTAGGGAAAACTAACGGACTACACAATGGGACTGAACACAATAAGACACACGTTATGACAACGCCTGATGACAGTCAGAGCCCTGAACCGCCAGTGAGCACAGCCCTGCCTGTCACACCGGACCCTTCCACTGTCACACCTACAACACCAGCCAGCGGATTAGAAGGCTCAGGAGAGGTTCACACATCCTCTCCAATCACCACCAAGGGTTTGTCTCTGCCGGGGGCTACATCTGAGCTCCCTGCGACTACTAGCATAGTCACTTCAGGTGCAAGTGATGCCGATTCTAGCACACAGGCAGCCAGAGACACCCCTAAACCATCAGTCCGCACGAGTCTGCCCAACAGCCCTAGCACACCATCCACACCACAAGGCACACACCATCCCGTGAGGAGTCTGCTTTCAGTCACGAGCCCTAAGCCAGAAGAAACACCAACACCGTCAAAATCAAGCAAAGATAGCTCAGCAACCAACAGTCCTCACCCAGCCGCCAGCAGACCAACAACCCCTCCCACAACAGCCCAGAGACCCGCTGAAAACAACAGCCACAACACCACCGAACAGCTTGAGTCCTTAACACAATTAGCAACTTCAGGTTCAATGATCTCTCCAACACAGACAGTCCTCCCAAAGAGTGTTACTTCTATAGCCATTCAAGACATTCATCCCAGCCCAACAAATAGGTCTAAAAGAAACCTTGATATGGAAATAATCT', + [fastaHeaderS]: + 'GTGTTCTCTTGAGTGTTGGCAAAATGGAAAACAAAATCGAGGTGAACAACAAAGATGAGATGAACAAATGGTTTGAGGAGTTCAAGAAAGGAAATGGACTTGTGGACACTTTCACAAACTCNTATTCCTTTTGTGAAAGCGTNCCAAATCTGGACAGNTTTGTNTTCCAGATGGCNAGTGCCACTGATGATGCACAAAANGANTCCATCTACGCATCTGCNCTGGTGGANGCAACCAAATTTTGTGCACCTATATACGAGTGTGCTTGGGCTAGCTCCACTGGCATTGTTAAAAAGGGACTGGAGTGGTTCGAGAAAAATGCAGGAACCATTAAATCCTGGGATGAGAGTTATACTGAGCTTAAAGTTGAAGTTCCCAAAATAGAACAACTCTCCAACTACCAGCAGGCTGCTCTCAAATGGAGAAAAGACATAGGCTTCCGTGTCAATGCAAATACGGCAGCTTTGAGTAACAAAGTCCTAGCAGAGTACAAAGTTCCTGGCGAGATTGTAATGTCTGTCAAAGAGATGTTGTCAGATATGATTAGAAGNAGGAACCTGATTCTCAACAGAGGTGGTGATGAGAACCCACGCGGCCCAGTTAGCCGTGAACATGTGGAGTGGTGC', }); await submissionPage.acceptTerms(); await submissionPage.submitSequence(); diff --git a/integration-tests/tests/specs/features/trim-ns.spec.ts b/integration-tests/tests/specs/features/trim-ns.spec.ts index b36733b16f..d9bcd1ad3b 100644 --- a/integration-tests/tests/specs/features/trim-ns.spec.ts +++ b/integration-tests/tests/specs/features/trim-ns.spec.ts @@ -22,8 +22,9 @@ test.describe('Sequence N trimming functionality', () => { const lSegmentWithNs = 'NNNNNNNNNNTTCAACAAGCAAAGCCAACTGTGACGGTGTTCTATATGCTAAAAGGTAACTTGATGAACACAGAGCCAACAGTTGCTGAGCTTGTCAGCTATGGTATAAAGGAAGGCAGGTTTTATAGGCTTTCCGACACCGGAATCAATGCAACCACATANNNNNN'; + const fastaHeaderL = 'TRIM_NS_TEST_L'; await submissionPage.fillSequenceData({ - L: lSegmentWithNs, + [fastaHeaderL]: lSegmentWithNs, }); await submissionPage.acceptTerms(); diff --git a/website/src/components/Edit/SequencesForm.spec.tsx b/website/src/components/Edit/SequencesForm.spec.tsx index 40d908fa00..9ee5cdc048 100644 --- a/website/src/components/Edit/SequencesForm.spec.tsx +++ b/website/src/components/Edit/SequencesForm.spec.tsx @@ -35,7 +35,7 @@ describe('SequencesForm', () => { makeReferenceGenomeLightweightSchema(['foo', 'bar']), ); - expect(emptyEditableSequences.getSequenceFasta('subId')).toBeUndefined(); + expect(emptyEditableSequences.getSequenceFasta()).toBeUndefined(); expect(emptyEditableSequences.getSequenceRecord()).deep.equals({}); }); @@ -45,39 +45,49 @@ describe('SequencesForm', () => { ); const initialRows = editableSequences.rows; expect(initialRows).toEqual([ - { label: 'Add a segment', value: null, initialValue: null, key: expect.any(String) }, + { label: 'Add a segment', value: null, initialValue: null, fastaHeader: null, key: expect.any(String) }, ]); const firstKey = initialRows[0].key; { - editableSequences = editableSequences.update(firstKey, 'ATCG', 'Segment 1'); - const fasta = editableSequences.getSequenceFasta('subId'); + editableSequences = editableSequences.update(firstKey, 'ATCG', 'Segment 1', 'subId_Segment1'); + const fasta = editableSequences.getSequenceFasta(); expect(fasta).not.toBeUndefined(); const fastaText = await fasta!.text(); - expect.soft(fastaText).toBe('>subId\nATCG'); + expect.soft(fastaText).toBe('>subId_Segment1\nATCG'); expect(editableSequences.getSequenceRecord()).deep.equals({ 'Segment 1': 'ATCG' }); const rows = editableSequences.rows; - expect(rows).toEqual([{ label: 'Segment 1', value: 'ATCG', initialValue: null, key: firstKey }]); + expect(rows).toEqual([ + { label: 'Segment 1', value: 'ATCG', initialValue: null, key: firstKey, fastaHeader: 'subId_Segment1' }, + ]); } - expect(() => editableSequences.update('another key', 'GG', 'another key')).toThrowError( + expect(() => editableSequences.update('another key', 'GG', 'another key', 'subId_anotherkey')).toThrowError( 'Maximum limit reached — you can add up to 1 sequence file(s) only.', ); - editableSequences = editableSequences.update(firstKey, null, null); + editableSequences = editableSequences.update(firstKey, null, null, null); expect(editableSequences.rows).toEqual([ - { label: 'Add a segment', value: null, initialValue: null, key: expect.any(String) }, + { label: 'Add a segment', value: null, fastaHeader: null, initialValue: null, key: expect.any(String) }, ]); const rowsAfterDeletion = editableSequences.rows; const newFirstKey = rowsAfterDeletion[0].key; { - editableSequences = editableSequences.update(newFirstKey, 'ATCG', 'Segment 1'); - const fasta = editableSequences.getSequenceFasta('subId'); + editableSequences = editableSequences.update(newFirstKey, 'ATCG', 'Segment 1', 'subId_Segment1'); + const fasta = editableSequences.getSequenceFasta(); expect(fasta).not.toBeUndefined(); const fastaText = await fasta!.text(); - expect.soft(fastaText).toBe('>subId\nATCG'); + expect.soft(fastaText).toBe('>subId_Segment1\nATCG'); expect(editableSequences.getSequenceRecord()).deep.equals({ 'Segment 1': 'ATCG' }); const rows = editableSequences.rows; - expect(rows).toEqual([{ label: 'Segment 1', value: 'ATCG', initialValue: null, key: newFirstKey }]); + expect(rows).toEqual([ + { + label: 'Segment 1', + value: 'ATCG', + initialValue: null, + key: newFirstKey, + fastaHeader: 'subId_Segment1', + }, + ]); } }); @@ -88,14 +98,14 @@ describe('SequencesForm', () => { const initialRows = editableSequences.rows; expect(initialRows).toEqual([ - { label: 'Add a segment', value: null, initialValue: null, key: expect.any(String) }, + { label: 'Add a segment', value: null, initialValue: null, fastaHeader: null, key: expect.any(String) }, ]); const firstKey = initialRows[0].key; let secondKey; { - editableSequences = editableSequences.update(firstKey, 'ATCG', 'Segment 1'); - const fasta = editableSequences.getSequenceFasta('subId'); + editableSequences = editableSequences.update(firstKey, 'ATCG', 'Segment 1', 'subId_Segment1'); + const fasta = editableSequences.getSequenceFasta(); expect(fasta).not.toBeUndefined(); const fastaText = await fasta!.text(); expect.soft(fastaText).toBe('>subId_Segment1\nATCG'); @@ -103,15 +113,15 @@ describe('SequencesForm', () => { const rows = editableSequences.rows; expect(rows).toEqual([ - { label: 'Segment 1', value: 'ATCG', initialValue: null, key: firstKey }, - { label: 'Add a segment', value: null, initialValue: null, key: expect.any(String) }, + { label: 'Segment 1', value: 'ATCG', initialValue: null, fastaHeader: 'subId_Segment1', key: firstKey }, + { label: 'Add a segment', value: null, initialValue: null, fastaHeader: null, key: expect.any(String) }, ]); secondKey = rows[1].key; } { - editableSequences = editableSequences.update(secondKey, 'TT', 'Segment 2'); - const fasta = editableSequences.getSequenceFasta('subId'); + editableSequences = editableSequences.update(secondKey, 'TT', 'Segment 2', 'subId_Segment2'); + const fasta = editableSequences.getSequenceFasta(); expect(fasta).not.toBeUndefined(); const fastaText = await fasta!.text(); expect.soft(fastaText).toBe('>subId_Segment1\nATCG\n>subId_Segment2\nTT'); @@ -119,12 +129,12 @@ describe('SequencesForm', () => { const rows = editableSequences.rows; expect(rows).deep.equals([ - { label: 'Segment 1', value: 'ATCG', initialValue: null, key: firstKey }, - { label: 'Segment 2', value: 'TT', initialValue: null, key: secondKey }, + { label: 'Segment 1', value: 'ATCG', initialValue: null, key: firstKey, fastaHeader: 'subId_Segment1' }, + { label: 'Segment 2', value: 'TT', initialValue: null, key: secondKey, fastaHeader: 'subId_Segment2' }, ]); } - expect(() => editableSequences.update('another key', 'GG', 'another key')).toThrowError( + expect(() => editableSequences.update('another key', 'GG', 'another key', 'anything')).toThrowError( 'Maximum limit reached — you can add up to 2 sequence file(s) only.', ); }); @@ -134,12 +144,12 @@ describe('SequencesForm', () => { const initialRows = editableSequences.rows; expect(initialRows).toEqual([ - { label: 'Add a segment', value: null, initialValue: null, key: expect.any(String) }, + { label: 'Add a segment', value: null, initialValue: null, fastaHeader: null, key: expect.any(String) }, ]); const key = initialRows[0].key; - editableSequences = editableSequences.update(key, 'ATCG', key); - const fasta = editableSequences.getSequenceFasta('subId'); + editableSequences = editableSequences.update(key, 'ATCG', key, 'subId'); + const fasta = editableSequences.getSequenceFasta(); expect(fasta).not.toBeUndefined(); const fastaText = await fasta!.text(); expect.soft(fastaText).toBe('>subId\nATCG'); @@ -147,9 +157,9 @@ describe('SequencesForm', () => { expect(editableSequences.getSequenceRecord()).deep.equals({ [key]: 'ATCG' }); const rows = editableSequences.rows; - expect(rows).deep.equals([{ label: key, value: 'ATCG', initialValue: null, key }]); + expect(rows).deep.equals([{ label: key, value: 'ATCG', initialValue: null, fastaHeader: 'subId', key }]); - expect(() => editableSequences.update('another key', 'GG', 'another key')).toThrowError( + expect(() => editableSequences.update('another key', 'GG', 'another key', 'anything')).toThrowError( 'Maximum limit reached — you can add up to 1 sequence file(s) only.', ); }); @@ -161,15 +171,15 @@ describe('SequencesForm', () => { const key = editableSequences.rows[0].key; - editableSequences = editableSequences.update(key, 'ATCG', key); + editableSequences = editableSequences.update(key, 'ATCG', key, key); expect(editableSequences.rows).toEqual([ - { label: key, value: 'ATCG', initialValue: null, key }, - { label: 'Add a segment', value: null, initialValue: null, key: expect.any(String) }, + { label: key, value: 'ATCG', initialValue: null, key, fastaHeader: key }, + { label: 'Add a segment', value: null, initialValue: null, fastaHeader: null, key: expect.any(String) }, ]); - editableSequences = editableSequences.update(key, null, null); + editableSequences = editableSequences.update(key, null, null, null); expect(editableSequences.rows).toEqual([ - { label: 'Add a segment', value: null, initialValue: null, key: expect.any(String) }, + { label: 'Add a segment', value: null, initialValue: null, fastaHeader: null, key: expect.any(String) }, ]); }); @@ -178,8 +188,8 @@ describe('SequencesForm', () => { defaultReviewData, makeReferenceGenomeLightweightSchema(['originalSequenceName', 'anotherSequenceName']), ); - editableSequences = editableSequences.update(editableSequences.rows[0].key, 'ATCG', 'label'); - const fasta = editableSequences.getSequenceFasta('subId'); + editableSequences = editableSequences.update(editableSequences.rows[0].key, 'ATCG', 'label', 'subId_label'); + const fasta = editableSequences.getSequenceFasta(); expect(fasta).not.toBeUndefined(); const fastaText = await fasta!.text(); expect.soft(fastaText).toBe('>subId_label\nATCG'); @@ -196,15 +206,16 @@ describe('SequencesForm', () => { expect(editableSequences.rows).toEqual([ { label: 'originalSequenceName', + fastaHeader: 'defaultSubmitter_originalSequenceName', value: 'originalUnalignedNucleotideSequencesValue', initialValue: 'originalUnalignedNucleotideSequencesValue', key: expect.any(String), }, - { label: 'Add a segment', value: null, initialValue: null, key: expect.any(String) }, + { label: 'Add a segment', value: null, initialValue: null, key: expect.any(String), fastaHeader: null }, ]); - editableSequences = editableSequences.update(editableSequences.rows[0].key, null, null); - const fasta = editableSequences.getSequenceFasta('subId'); + editableSequences = editableSequences.update(editableSequences.rows[0].key, null, null, null); + const fasta = editableSequences.getSequenceFasta(); expect(fasta).toBeUndefined(); expect(editableSequences.getSequenceRecord()).deep.equals({}); @@ -213,6 +224,7 @@ describe('SequencesForm', () => { { label: 'Add a segment', value: null, + fastaHeader: null, initialValue: null, key: expect.any(String), }, diff --git a/website/src/components/Edit/SequencesForm.tsx b/website/src/components/Edit/SequencesForm.tsx index ef27d814a2..9b10589bab 100644 --- a/website/src/components/Edit/SequencesForm.tsx +++ b/website/src/components/Edit/SequencesForm.tsx @@ -5,25 +5,29 @@ import type { ReferenceGenomesLightweightSchema } from '../../types/referencesGe import { FileUploadComponent } from '../Submission/FileUpload/FileUploadComponent.tsx'; import { PLAIN_SEGMENT_KIND, VirtualFile } from '../Submission/FileUpload/fileProcessing.ts'; -function generateAndDownloadFastaFile( - accessionVersion: string, - sequenceData: string, - segmentKey?: string, - isSingleSegment: boolean = false, -) { - const fileName = - isSingleSegment || !segmentKey ? `${accessionVersion}.fasta` : `${accessionVersion}_${segmentKey}.fasta`; - - const header = isSingleSegment || !segmentKey ? accessionVersion : `${accessionVersion}_${segmentKey}`; +function generateAndDownloadFastaFile(fastaHeader: string | null, sequenceData: string | null) { + let fileContent = ''; + let trimmedHeader = ''; + + if (fastaHeader === null && sequenceData === null) { + fileContent = ''; + } else { + if (fastaHeader === null) { + throw new Error( + 'Internal Error: sequenceData exists but fastaHeader is empty - contact your administrator', + ); + } - const fileContent = `>${header}\n${sequenceData}`; + trimmedHeader = fastaHeader.replace(/\s+/g, ''); + fileContent = `>${trimmedHeader}\n${sequenceData ?? ''}`; + } const blob = new Blob([fileContent], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; - a.download = fileName; + a.download = `${trimmedHeader || 'sequence'}.fasta`; a.click(); URL.revokeObjectURL(url); @@ -32,6 +36,7 @@ function generateAndDownloadFastaFile( type EditableSequenceFile = { key: string; label: string; + fastaHeader: string | null; value: string | null; initialValue: string | null; }; @@ -50,6 +55,7 @@ export class EditableSequences { if (rows.length < this.maxNumberOfRows) { rows.push({ label: `Add a segment`, + fastaHeader: null, value: null, initialValue: null, key: EditableSequences.getNextKey(), @@ -75,15 +81,21 @@ export class EditableSequences { initialData: SequenceEntryToEdit, referenceGenomeLightweightSchema: ReferenceGenomesLightweightSchema, ): EditableSequences { + const maxNumberRows = this.getMaxNumberOfRows(referenceGenomeLightweightSchema); const existingDataRows = Object.entries(initialData.originalData.unalignedNucleotideSequences).map( ([key, value]) => ({ - label: key, + // TODO: for now key corresponds to the segment name in future it will be the fastaHeader + label: key, // TODO: In future prepro will map the fastaHeader to the segment (will be added to the label) + fastaHeader: + maxNumberRows > 1 + ? `${initialData.submissionId}_${key.replace(/\s+/g, '')}` + : initialData.submissionId, // TODO: in future will come from the key value: value, initialValue: value, - key: EditableSequences.getNextKey(), //TODO: check if this is buggy + key: EditableSequences.getNextKey(), }), ); - return new EditableSequences(existingDataRows, this.getMaxNumberOfRows(referenceGenomeLightweightSchema)); + return new EditableSequences(existingDataRows, maxNumberRows); } /** @@ -109,7 +121,7 @@ export class EditableSequences { /** * Create a new {@link EditableSequences} object with the given row value updated. */ - update(key: string, value: string | null, label: string | null): EditableSequences { + update(key: string, value: string | null, label: string | null, fastaHeader: string | null): EditableSequences { const existingFileIndex = this.editableSequenceFiles.findIndex((file) => file.key === key); if (existingFileIndex === -1 && this.editableSequenceFiles.length === this.maxNumberOfRows) { @@ -117,12 +129,18 @@ export class EditableSequences { } label ??= value == null ? 'Add a segment' : key; + fastaHeader ??= value == null ? null : key; // Ensure fastaHeader is never null if a sequence exists + const existingFastaHeaders = this.editableSequenceFiles.map((sequence) => sequence.fastaHeader); + if (existingFastaHeaders.includes(fastaHeader)) { + throw new Error(`A sequence with the fastaHeader ${fastaHeader} already exists.`); + } const newSequenceFiles = [...this.editableSequenceFiles]; newSequenceFiles[existingFileIndex > -1 ? existingFileIndex : this.editableSequenceFiles.length] = { ...(existingFileIndex > -1 ? newSequenceFiles[existingFileIndex] : { key, initialValue: null }), value: value, label: label, + fastaHeader: fastaHeader, }; return new EditableSequences( @@ -131,29 +149,30 @@ export class EditableSequences { ); } - getSequenceFasta(submissionId: string): File | undefined { + getSequenceFasta(): File | undefined { const filledRows = this.rows.filter((row) => row.value !== null); if (filledRows.length === 0) { return undefined; } - const fastaContent = !this.isMultiSegmented() - ? `>${submissionId}\n${filledRows[0].value}` - : filledRows - .map( - (sequence) => - `>${submissionId}_${sequence.label.replaceAll(/[^a-zA-Z0-9]/g, '')}\n${sequence.value}`, - ) - .join('\n'); + const fastaContent = filledRows.map((sequence) => `>${sequence.fastaHeader}\n${sequence.value}`).join('\n'); return new File([fastaContent], 'sequences.fasta', { type: 'text/plain' }); } getSequenceRecord(): Record { - return this.rows - .filter((row) => row.value !== null) - .reduce((prev, row) => ({ ...prev, [row.label]: row.value }), {}); + const filledRows = this.rows.filter( + ( + row, + ): row is Omit & { fastaHeader: string; value: string } => + row.value !== null && row.fastaHeader !== null, + ); + + return filledRows.reduce>((prev, row) => { + prev[row.label] = row.value; //TODO: this will have to be changed to fastaHeader in future + return prev; + }, {}); } } @@ -182,9 +201,9 @@ export const SequencesForm: FC = ({ { const text = file ? await file.text() : null; - const header = file ? await file.header() : null; + const fastaHeader = file ? await file.header() : null; setEditableSequences((editableSequences) => - editableSequences.update(field.key, text, header), + editableSequences.update(field.key, text, fastaHeader, fastaHeader), ); }} name={`${field.label}_segment_file`} @@ -200,13 +219,7 @@ export const SequencesForm: FC = ({ onDownload={ field.initialValue !== null && dataToEdit ? () => { - const accessionVersion = `${dataToEdit.accession}.${dataToEdit.version}`; - generateAndDownloadFastaFile( - accessionVersion, - field.value ?? '', - field.label, - !multiSegment, - ); + generateAndDownloadFastaFile(field.fastaHeader, field.value); } : undefined } diff --git a/website/src/components/Submission/FormOrUploadWrapper.tsx b/website/src/components/Submission/FormOrUploadWrapper.tsx index 40e84afc95..01a322c52d 100644 --- a/website/src/components/Submission/FormOrUploadWrapper.tsx +++ b/website/src/components/Submission/FormOrUploadWrapper.tsx @@ -89,7 +89,7 @@ export const FormOrUploadWrapper: FC = ({ if (!metadataFile) { return { type: 'error', errorMessage: 'Please specify metadata.' }; } - const sequenceFile = editableSequences.getSequenceFasta(submissionId); + const sequenceFile = editableSequences.getSequenceFasta(); if (!sequenceFile && enableConsensusSequences) { return { type: 'error', errorMessage: 'Please enter sequence data.' }; } From 03e383c63ec0e53b11429aecc8f6f85050d7589a Mon Sep 17 00:00:00 2001 From: anna-parker <50943381+anna-parker@users.noreply.github.com> Date: Wed, 19 Nov 2025 20:55:14 +0100 Subject: [PATCH 09/44] feat: add some suggestions --- website/src/components/Edit/EditPage.tsx | 2 +- .../components/Edit/SequencesForm.spec.tsx | 37 +++++++++++++- website/src/components/Edit/SequencesForm.tsx | 50 ++++++++----------- 3 files changed, 59 insertions(+), 30 deletions(-) diff --git a/website/src/components/Edit/EditPage.tsx b/website/src/components/Edit/EditPage.tsx index c37dfbb5ed..f9e934b00e 100644 --- a/website/src/components/Edit/EditPage.tsx +++ b/website/src/components/Edit/EditPage.tsx @@ -88,7 +88,7 @@ const InnerEditPage: FC = ({ }); return; } - const sequenceFile = editableSequences.getSequenceFasta(dataToEdit.submissionId); + const sequenceFile = editableSequences.getSequenceFasta(); if (!sequenceFile) { toast.error('Please enter a sequence.', { position: 'top-center', diff --git a/website/src/components/Edit/SequencesForm.spec.tsx b/website/src/components/Edit/SequencesForm.spec.tsx index 9ee5cdc048..8b09d7c05f 100644 --- a/website/src/components/Edit/SequencesForm.spec.tsx +++ b/website/src/components/Edit/SequencesForm.spec.tsx @@ -1,4 +1,5 @@ -import { describe, expect, test } from 'vitest'; +import { toast } from 'react-toastify'; +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; import { EditableSequences } from './SequencesForm'; import { defaultReviewData } from '../../../vitest.setup'; @@ -30,6 +31,13 @@ function makeSubOrganismReferenceSchema(suborganisms: string[]): ReferenceGenome /* eslint-disable @typescript-eslint/naming-convention -- this test has keys that expectedly contain spaces */ describe('SequencesForm', () => { + beforeEach(() => { + vi.spyOn(toast, 'error'); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); test('Empty editable sequences produces no output', () => { const emptyEditableSequences = EditableSequences.fromSequenceNames( makeReferenceGenomeLightweightSchema(['foo', 'bar']), @@ -139,6 +147,33 @@ describe('SequencesForm', () => { ); }); + test('GIVEN a multi-segmented organism THEN do not allow duplicate fasta headers', () => { + let editableSequences = EditableSequences.fromSequenceNames( + makeReferenceGenomeLightweightSchema(['foo', 'bar']), + ); + + const initialRows = editableSequences.rows; + expect(initialRows).toEqual([ + { label: 'Add a segment', value: null, initialValue: null, fastaHeader: null, key: expect.any(String) }, + ]); + const firstKey = initialRows[0].key; + + editableSequences = editableSequences.update(firstKey, 'ATCG', 'Segment 1', 'subId_Segment'); + const rowsAfterFirstUpdate = editableSequences.rows; + const secondKey = rowsAfterFirstUpdate[1].key; + + editableSequences = editableSequences.update(secondKey, 'TT', 'Segment 2', 'subId_Segment'); + + const errorMessage = 'A sequence with the fastaHeader subId_Segment already exists.'; + expect(toast.error).toHaveBeenCalledWith(expect.stringContaining(errorMessage)); + + // Expect that the second sequence was not added + expect(editableSequences.rows).toEqual([ + { label: 'Segment 1', value: 'ATCG', initialValue: null, key: firstKey, fastaHeader: 'subId_Segment' }, + { label: 'Add a segment', value: null, initialValue: null, fastaHeader: null, key: expect.any(String) }, + ]); + }); + test('GIVEN a single-segmented organism THEN only allows 1 input and fasta header does not contain the segment name', async () => { let editableSequences = EditableSequences.fromSequenceNames(makeReferenceGenomeLightweightSchema(['foo'])); diff --git a/website/src/components/Edit/SequencesForm.tsx b/website/src/components/Edit/SequencesForm.tsx index 9b10589bab..1f9ac22653 100644 --- a/website/src/components/Edit/SequencesForm.tsx +++ b/website/src/components/Edit/SequencesForm.tsx @@ -1,33 +1,21 @@ import { type Dispatch, type FC, type SetStateAction } from 'react'; +import { toast } from 'react-toastify'; import { type SequenceEntryToEdit } from '../../types/backend.ts'; import type { ReferenceGenomesLightweightSchema } from '../../types/referencesGenomes.ts'; import { FileUploadComponent } from '../Submission/FileUpload/FileUploadComponent.tsx'; import { PLAIN_SEGMENT_KIND, VirtualFile } from '../Submission/FileUpload/fileProcessing.ts'; -function generateAndDownloadFastaFile(fastaHeader: string | null, sequenceData: string | null) { - let fileContent = ''; - let trimmedHeader = ''; - - if (fastaHeader === null && sequenceData === null) { - fileContent = ''; - } else { - if (fastaHeader === null) { - throw new Error( - 'Internal Error: sequenceData exists but fastaHeader is empty - contact your administrator', - ); - } - - trimmedHeader = fastaHeader.replace(/\s+/g, ''); - fileContent = `>${trimmedHeader}\n${sequenceData ?? ''}`; - } +function generateAndDownloadFastaFile(fastaHeader: string, sequenceData: string) { + const trimmedHeader = fastaHeader.replace(/\s+/g, ''); + const fileContent = `>${trimmedHeader}\n${sequenceData}`; const blob = new Blob([fileContent], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; - a.download = `${trimmedHeader || 'sequence'}.fasta`; + a.download = `${trimmedHeader}.fasta`; a.click(); URL.revokeObjectURL(url); @@ -35,7 +23,7 @@ function generateAndDownloadFastaFile(fastaHeader: string | null, sequenceData: type EditableSequenceFile = { key: string; - label: string; + label: string | null; fastaHeader: string | null; value: string | null; initialValue: string | null; @@ -48,9 +36,9 @@ export class EditableSequences { private readonly maxNumberOfRows: number; public get rows(): Required[] { - const rows = this.editableSequenceFiles.map((row, _) => ({ + const rows = this.editableSequenceFiles.map((row, i) => ({ ...row, - label: row.label, + label: row.label ?? `Segment ${i + 1}`, })); if (rows.length < this.maxNumberOfRows) { rows.push({ @@ -128,11 +116,13 @@ export class EditableSequences { throw new Error(`Maximum limit reached — you can add up to ${this.maxNumberOfRows} sequence file(s) only.`); } - label ??= value == null ? 'Add a segment' : key; fastaHeader ??= value == null ? null : key; // Ensure fastaHeader is never null if a sequence exists - const existingFastaHeaders = this.editableSequenceFiles.map((sequence) => sequence.fastaHeader); - if (existingFastaHeaders.includes(fastaHeader)) { - throw new Error(`A sequence with the fastaHeader ${fastaHeader} already exists.`); + if (this.editableSequenceFiles.some((seq) => seq.fastaHeader === fastaHeader)) { + toast.error(`A sequence with the fastaHeader ${fastaHeader} already exists.`); + return new EditableSequences( + this.editableSequenceFiles.filter((file) => file.value !== null), + this.maxNumberOfRows, + ); } const newSequenceFiles = [...this.editableSequenceFiles]; @@ -165,8 +155,11 @@ export class EditableSequences { const filledRows = this.rows.filter( ( row, - ): row is Omit & { fastaHeader: string; value: string } => - row.value !== null && row.fastaHeader !== null, + ): row is Omit & { + fastaHeader: string; + value: string; + label: string; + } => row.value !== null && row.fastaHeader !== null && row.label !== null, ); return filledRows.reduce>((prev, row) => { @@ -217,9 +210,10 @@ export const SequencesForm: FC = ({ } showUndo={field.initialValue !== null} onDownload={ - field.initialValue !== null && dataToEdit + field.initialValue !== null && field.value !== null && dataToEdit ? () => { - generateAndDownloadFastaFile(field.fastaHeader, field.value); + if (field.value === null) return; + generateAndDownloadFastaFile(field.fastaHeader ?? 'sequence', field.value); } : undefined } From ea37faf0643efa2d02882f063d93616ac616ba4a Mon Sep 17 00:00:00 2001 From: "Anna (Anya) Parker" <50943381+anna-parker@users.noreply.github.com> Date: Thu, 20 Nov 2025 14:32:22 +0100 Subject: [PATCH 10/44] feat!(backend): refactor multi-segment submission (2/n) (#5398) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolves https://github.com/loculus-project/loculus/issues/4708, https://github.com/loculus-project/loculus/issues/4734 partially resolves https://github.com/loculus-project/loculus/issues/5392, https://github.com/loculus-project/loculus/issues/5185#issuecomment-3383043241 Builds on https://github.com/loculus-project/loculus/pull/5382 When users submit to multi-segmented organisms and want to group multiple segments under one metadata entry they are now required to add an additional `fastaId` column with a space (or comma) -separated list of the `fastaIds` (fasta header IDs) of the respective sequences. If no `fastaId` column is supplied the `submissionId` will be used instead and the backend will assume that (as in the single-segmented case) there is a one-to-one mapping of metadata `submissionId` to `fastaId`. This new submission structure was voted for in microbioinfo: https://microbial-bioinfo.slack.com/archives/CB0HYT53M/p1760961465729399 and discussed in https://app.nuclino.com/Loculus/Development/2025-10-20-Weekly-6d5fe89f-8ded-4286-b892-d215e0a498f6 (and in other meetings) Nextclade sort will be used to assign segments/subtypes for all aligned sequences: ``` minimizer_index: ``` For organisms without a nextclade dataset we still allow the fasta headers to be used to determine the segment/subtype - entries must have the format _ (as in current set up). As preprocessing now assigns segments it will return a map from the segment (or subtype) to the fastaHeader in the processedData: `sequenceNameToFastaHeaderMap`. This allows us to surface this assignment on the edit page. You can use https://github.com/pathoplexus/dev_example_data/pull/2 for testing. Instead of having a dictionary for the nextclade datasets and servers we make `nucleotideSequences` a list of sequences: ``` nextclade_dataset_name: L: nextstrain/cchfv/linked/L M: nextstrain/cchfv/linked/M S: nextstrain/cchfv/linked/S nextclade_dataset_server: https://raw.githubusercontent.com/nextstrain/nextclade_data/cornelius-cchfv/data_output genes: [RdRp, GPC, NP] ``` ``` nucleotideSequences: - name: L nextclade_dataset_name: nextstrain/cchfv/linked/L nextclade_dataset_tag: nextclade_dataset_server: accepted_sort_matches: gene_prefix: - name: M nextclade_dataset_name: nextstrain/cchfv/linked/M - name: S nextclade_dataset_name: nextstrain/cchfv/linked/S nextclade_dataset_server: https://raw.githubusercontent.com/nextstrain/nextclade_data/cornelius-cchfv/data_output ``` Note the templates now also generate the genes list from the merged config. - [ ] Update values.schema.json - [x] keep tests for alignment NONE case - [x] Create a minimizer for tests using: https://github.com/loculus-project/nextclade-sort-minimizer-creator - [x] Any manual testing that has been done is documented: submission of EVs from test folder were submitted with the same fastaHeader as the submissionId -> this succeeded, additionally the submission of CCHF with a fastaID column in the metadata was tested (also in folder above), additionally revision of a segment was tested - [x] Have preprocessing send back a segment: fastaHeader mapping - [ ] add integration testing for full EV submission user journey - [ ] improve CCHF minimizer (some segments are again not assigned) - [ ] discuss if the originalData dictionary should be migrated (persistent DB has segmentName as key, now we have fastaHeader as key) - [ ] update PPX docs with new multi-segment submission format 🚀 Preview: https://multi-segment-submission.loculus.org --------- Co-authored-by: Cornelius Roemer --- backend/docs/db/schema.sql | 17 +- .../loculus/backend/api/SubmissionTypes.kt | 10 +- .../SubmissionControllerDescriptions.kt | 16 +- .../org/loculus/backend/model/SubmitModel.kt | 68 +-- .../service/submission/CompressionService.kt | 2 + .../submission/EmptyProcessedDataProvider.kt | 1 + .../ProcessedSequenceEntryValidator.kt | 5 + .../submission/SubmissionDatabaseService.kt | 3 +- .../submission/UploadDatabaseService.kt | 75 ++- .../dbtables/MetadataUploadAuxTable.kt | 1 + .../dbtables/SequenceUploadAuxTable.kt | 5 +- .../org/loculus/backend/utils/FastaReader.kt | 16 +- .../loculus/backend/utils/MetadataEntry.kt | 79 ++- .../loculus/backend/utils/ParseFastaHeader.kt | 40 -- .../migration/V1.22__refactor_aux_tables.sql | 12 + .../submission/PreparedOriginalData.kt | 2 +- .../submission/PreparedProcessedData.kt | 3 + .../submission/ReviseEndpointTest.kt | 41 +- .../submission/SubmissionConvenienceClient.kt | 6 +- .../SubmitEndpointSingleSegmentedTest.kt | 90 ---- .../submission/SubmitEndpointTest.kt | 175 ++++--- .../controller/submission/SubmitFiles.kt | 39 +- .../ProcessedMetadataPostprocessorTest.kt | 1 + .../ProcessedSequencesPostprocessorTest.kt | 4 + .../utils/EarliestReleaseDateFinderTest.kt | 1 + .../loculus/backend/utils/FastaReaderTest.kt | 10 +- .../backend/utils/MetadataEntryTest.kt | 12 +- .../test/resources/metadata_multi_segment.tsv | 11 + .../revised_metadata_multi_segment.tsv | 11 + ingest/scripts/heuristic_group_segments.py | 9 +- .../expected_output_cchf/revise_metadata.tsv | 4 +- .../expected_output_cchf/submit_metadata.tsv | 4 +- .../tests/fixtures/sequence.fixture.ts | 9 +- integration-tests/tests/readonly.setup.ts | 5 +- .../tests/specs/cli/end-to-end-flow.spec.ts | 6 +- .../specs/features/revise-sequence.spec.ts | 22 +- .../search/override-hidden-fields.spec.ts | 6 +- .../features/sequence-view.dependent.spec.ts | 12 +- .../specs/features/single-submit.spec.ts | 10 +- .../specs/features/submission-flow.spec.ts | 12 +- .../tests/specs/features/trim-ns.spec.ts | 3 +- .../tests/test-data/cchfv_test_metadata.tsv | 4 +- .../loculus-preprocessing-config.yaml | 10 +- kubernetes/loculus/values.schema.json | 2 +- kubernetes/loculus/values.yaml | 38 +- .../src/loculus_preprocessing/config.py | 74 ++- .../src/loculus_preprocessing/datatypes.py | 10 + .../src/loculus_preprocessing/nextclade.py | 478 ++++++++++++++---- .../src/loculus_preprocessing/prepro.py | 39 +- .../ebola-dataset/minimizer/minimizer.json | 1 + .../nextclade/tests/factory_methods.py | 10 +- .../tests/multi_pathogen_config.yaml | 12 +- .../nextclade/tests/multi_segment_config.yaml | 12 +- .../tests/multi_segment_config_unaligned.yaml | 23 + .../tests/single_segment_config.yaml | 6 +- .../tests/test_nextclade_preprocessing.py | 312 ++++++++++-- preprocessing/specification.md | 1 + website/src/components/Edit/EditPage.tsx | 7 +- website/src/components/Edit/MetadataForm.tsx | 9 +- .../components/Edit/SequencesForm.spec.tsx | 16 +- website/src/components/Edit/SequencesForm.tsx | 49 +- .../ReviewPage/SequencesDialog.spec.tsx | 4 + .../SequenceEntryUploadComponent.tsx | 23 +- .../Submission/FormOrUploadWrapper.tsx | 4 +- website/src/settings.ts | 1 + website/src/types/backend.ts | 1 + website/vitest.setup.ts | 5 +- 67 files changed, 1360 insertions(+), 649 deletions(-) delete mode 100644 backend/src/main/kotlin/org/loculus/backend/utils/ParseFastaHeader.kt create mode 100644 backend/src/main/resources/db/migration/V1.22__refactor_aux_tables.sql delete mode 100644 backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointSingleSegmentedTest.kt create mode 100644 backend/src/test/resources/metadata_multi_segment.tsv create mode 100644 backend/src/test/resources/revised_metadata_multi_segment.tsv create mode 100644 preprocessing/nextclade/tests/ebola-dataset/minimizer/minimizer.json create mode 100644 preprocessing/nextclade/tests/multi_segment_config_unaligned.yaml diff --git a/backend/docs/db/schema.sql b/backend/docs/db/schema.sql index 5e67029a33..ec22ccc37c 100644 --- a/backend/docs/db/schema.sql +++ b/backend/docs/db/schema.sql @@ -378,7 +378,8 @@ CREATE TABLE public.metadata_upload_aux_table ( group_id integer, uploaded_at timestamp without time zone NOT NULL, metadata jsonb NOT NULL, - files jsonb + files jsonb, + fasta_ids jsonb DEFAULT '[]'::jsonb ); @@ -538,9 +539,8 @@ ALTER VIEW public.sequence_entries_view OWNER TO postgres; CREATE TABLE public.sequence_upload_aux_table ( upload_id text NOT NULL, - submission_id text NOT NULL, - segment_name text NOT NULL, - compressed_sequence_data text NOT NULL + compressed_sequence_data text NOT NULL, + fasta_id text NOT NULL ); @@ -753,7 +753,7 @@ ALTER TABLE ONLY public.sequence_entries_preprocessed_data -- ALTER TABLE ONLY public.sequence_upload_aux_table - ADD CONSTRAINT sequence_upload_aux_table_pkey PRIMARY KEY (upload_id, submission_id, segment_name); + ADD CONSTRAINT sequence_upload_aux_table_pkey PRIMARY KEY (upload_id, fasta_id); -- @@ -794,6 +794,13 @@ CREATE INDEX data_use_terms_table_accession_idx ON public.data_use_terms_table U CREATE INDEX flyway_schema_history_s_idx ON public.flyway_schema_history USING btree (success); +-- +-- Name: metadata_upload_aux_table_fasta_ids_idx; Type: INDEX; Schema: public; Owner: postgres +-- + +CREATE INDEX metadata_upload_aux_table_fasta_ids_idx ON public.metadata_upload_aux_table USING gin (fasta_ids jsonb_path_ops); + + -- -- Name: sequence_entries_organism_idx; Type: INDEX; Schema: public; Owner: postgres -- diff --git a/backend/src/main/kotlin/org/loculus/backend/api/SubmissionTypes.kt b/backend/src/main/kotlin/org/loculus/backend/api/SubmissionTypes.kt index fb165c734c..6352e85723 100644 --- a/backend/src/main/kotlin/org/loculus/backend/api/SubmissionTypes.kt +++ b/backend/src/main/kotlin/org/loculus/backend/api/SubmissionTypes.kt @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.JsonDeserializer import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.annotation.JsonDeserialize import io.swagger.v3.oas.annotations.media.Schema +import org.loculus.backend.model.FastaId import org.loculus.backend.model.SubmissionId import org.loculus.backend.service.files.FileId import org.loculus.backend.utils.Accession @@ -166,6 +167,11 @@ data class ProcessedData( description = "The key is the gene name, the value is a list of amino acid insertions", ) val aminoAcidInsertions: Map>, + @Schema( + example = """{"segment1": "fastaHeader1", "segment2": "fastaHeader2"}""", + description = "The key is the segment name, the value is the fastaHeader of the original Data", + ) + val sequenceNameToFastaHeaderMap: Map = emptyMap(), @Schema( example = """{"raw_reads": [{"fileId": "s0m3-uUiDd", "name": "data.fastaq"}], "sequencing_logs": []}""", description = "The key is the file category name, the value is a list of files, with ID and name.", @@ -300,9 +306,9 @@ data class OriginalDataInternal( val metadata: Map, @Schema( example = "{\"segment1\": \"ACTG\", \"segment2\": \"GTCA\"}", - description = "The key is the segment name, the value is the nucleotide sequence", + description = "The key is the fastaID, the value is the nucleotide sequence", ) - val unalignedNucleotideSequences: Map, + val unalignedNucleotideSequences: Map, @Schema( example = """{"raw_reads": [{"fileId": "f1le-uuId-asdf", "name": "myfile.fastaq"]}""", description = "A map from file categories, to lists of files. The files can also have URLs.", diff --git a/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionControllerDescriptions.kt b/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionControllerDescriptions.kt index 36c01350ae..44070c5785 100644 --- a/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionControllerDescriptions.kt +++ b/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionControllerDescriptions.kt @@ -1,13 +1,13 @@ package org.loculus.backend.controller -import org.loculus.backend.model.HEADER_TO_CONNECT_METADATA_AND_SEQUENCES +import org.loculus.backend.model.METADATA_ID_HEADER const val SUBMIT_RESPONSE_DESCRIPTION = """ Returns a list of accession, version and submissionId of the submitted sequence entries. -The submissionId is the (locally unique) '$HEADER_TO_CONNECT_METADATA_AND_SEQUENCES' provided by the submitter in the metadata file. +The submissionId is the (locally unique) '$METADATA_ID_HEADER' provided by the submitter in the metadata file. The version will be 1 for every sequence. The accession is the (globally unique) id that the system assigned to the sequence entry. -You can use this response to associate the user provided $HEADER_TO_CONNECT_METADATA_AND_SEQUENCES with the system assigned accession. +You can use this response to associate the user provided $METADATA_ID_HEADER with the system assigned accession. """ const val SUBMIT_ERROR_RESPONSE = """ @@ -18,16 +18,18 @@ const val METADATA_FILE_DESCRIPTION = """ A TSV (tab separated values) file containing the metadata of the submitted sequence entries. The file may be compressed with zstd, xz, zip, gzip, lzma, bzip2 (with common extensions). It must contain the column names. -The field '$HEADER_TO_CONNECT_METADATA_AND_SEQUENCES' is required and must be unique within the provided dataset. +The field '$METADATA_ID_HEADER' is required and must be unique within the provided dataset. It is used to associate metadata to the sequences in the sequences fasta file. """ + +// TODO: update description const val SEQUENCE_FILE_DESCRIPTION = """ A fasta file containing the unaligned nucleotide sequences of the submitted sequences. The file may be compressed with zstd, xz, zip, gzip, lzma, bzip2 (with common extensions). If the underlying organism has a single segment, -the headers of the fasta file must match the '$HEADER_TO_CONNECT_METADATA_AND_SEQUENCES' field in the metadata file. +the headers of the fasta file must match the '$METADATA_ID_HEADER' field in the metadata file. If the underlying organism has multiple segments, -the headers of the fasta file must be of the form '>[$HEADER_TO_CONNECT_METADATA_AND_SEQUENCES]_[segmentName]'. +the headers of the fasta file must be of the form '>[$METADATA_ID_HEADER]_[segmentName]'. """ const val FILE_MAPPING_DESCRIPTION = """ @@ -114,7 +116,7 @@ The version will increase by one in respect to the original accession version. const val REVISED_METADATA_FILE_DESCRIPTION = """ A TSV (tab separated values) file containing the metadata of the revised data. -The first row must contain the column names. The column '$HEADER_TO_CONNECT_METADATA_AND_SEQUENCES' is required and must be unique within the +The first row must contain the column names. The column '$METADATA_ID_HEADER' is required and must be unique within the provided dataset. It is used to associate metadata to the sequences in the sequences fasta file. Additionally, the column 'accession' is required and must match the accession of the original sequence entry. """ diff --git a/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt b/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt index a00abd6c1a..c437c38dd2 100644 --- a/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt +++ b/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt @@ -31,13 +31,15 @@ import java.io.BufferedInputStream import java.io.File import java.io.InputStream -const val HEADER_TO_CONNECT_METADATA_AND_SEQUENCES = "id" -const val HEADER_TO_CONNECT_METADATA_AND_SEQUENCES_ALTERNATE_FOR_BACKCOMPAT = "submissionId" +const val METADATA_ID_HEADER = "id" +const val METADATA_ID_HEADER_ALTERNATE_FOR_BACKCOMPAT = "submissionId" +const val FASTA_ID_HEADER = "fastaId" const val ACCESSION_HEADER = "accession" private val log = KotlinLogging.logger { } typealias SubmissionId = String +typealias FastaId = String typealias SegmentName = String const val UNIQUE_CONSTRAINT_VIOLATION_SQL_STATE = "23505" @@ -126,8 +128,13 @@ class SubmitModel( val metadataSubmissionIds = uploadDatabaseService.getMetadataUploadSubmissionIds(uploadId).toSet() if (requiresConsensusSequenceFile(submissionParams.organism)) { log.debug { "Validating submission with uploadId $uploadId" } - val sequenceSubmissionIds = uploadDatabaseService.getSequenceUploadSubmissionIds(uploadId).toSet() - validateSubmissionIdSetsForConsensusSequences(metadataSubmissionIds, sequenceSubmissionIds) + val metadataFastaIds = uploadDatabaseService.getFastaIdsForMetadata(uploadId).flatten() + val metadataFastaIdsSet = metadataFastaIds.toSet() + if (metadataFastaIdsSet.size < metadataFastaIds.size) { + throw UnprocessableEntityException("Metadata file contains duplicate fastaIds.") + } + val sequenceFastaIds = uploadDatabaseService.getSequenceUploadSubmissionIds(uploadId).toSet() + validateSubmissionIdSetsForConsensusSequences(metadataFastaIdsSet, sequenceFastaIds) } if (submissionParams is SubmissionParams.RevisionSubmissionParams) { @@ -167,38 +174,39 @@ class SubmitModel( metadataFileTypes, metadataTempFileToDelete, ) + val addFastaId = requiresConsensusSequenceFile(submissionParams.organism) try { - uploadMetadata(uploadId, submissionParams, metadataStream, batchSize) + uploadMetadata(uploadId, submissionParams, metadataStream, batchSize, addFastaId = addFastaId) } finally { metadataTempFileToDelete.delete() } val sequenceFile = submissionParams.sequenceFile if (sequenceFile == null) { - if (requiresConsensusSequenceFile(submissionParams.organism)) { + if (addFastaId) { throw BadRequestException( "Submissions for organism ${submissionParams.organism.name} require a sequence file.", ) } - } else { - if (!requiresConsensusSequenceFile(submissionParams.organism)) { - throw BadRequestException( - "Sequence uploads are not allowed for organism ${submissionParams.organism.name}.", - ) - } + return + } + if (!addFastaId) { + throw BadRequestException( + "Sequence uploads are not allowed for organism ${submissionParams.organism.name}.", + ) + } - val sequenceTempFileToDelete = MaybeFile() - try { - val sequenceStream = getStreamFromFile( - sequenceFile, - uploadId, - sequenceFileTypes, - sequenceTempFileToDelete, - ) - uploadSequences(uploadId, sequenceStream, batchSize, submissionParams.organism) - } finally { - sequenceTempFileToDelete.delete() - } + val sequenceTempFileToDelete = MaybeFile() + try { + val sequenceStream = getStreamFromFile( + sequenceFile, + uploadId, + sequenceFileTypes, + sequenceTempFileToDelete, + ) + uploadSequences(uploadId, sequenceStream, batchSize, submissionParams.organism) + } finally { + sequenceTempFileToDelete.delete() } } @@ -244,6 +252,7 @@ class SubmitModel( submissionParams: SubmissionParams, metadataStream: InputStream, batchSize: Int, + addFastaId: Boolean, ) { log.debug { "intermediate storing uploaded metadata of type ${submissionParams.uploadType.name} " + @@ -253,7 +262,7 @@ class SubmitModel( try { when (submissionParams) { is SubmissionParams.OriginalSubmissionParams -> { - metadataEntryStreamAsSequence(metadataStream) + metadataEntryStreamAsSequence(metadataStream, addFastaId) .chunked(batchSize) .forEach { batch -> uploadDatabaseService.batchInsertMetadataInAuxTable( @@ -269,7 +278,7 @@ class SubmitModel( } is SubmissionParams.RevisionSubmissionParams -> { - revisionEntryStreamAsSequence(metadataStream) + revisionEntryStreamAsSequence(metadataStream, addFastaId) .chunked(batchSize) .forEach { batch -> uploadDatabaseService.batchInsertRevisedMetadataInAuxTable( @@ -344,14 +353,15 @@ class SubmitModel( if (metadataKeysNotInSequences.isNotEmpty() || sequenceKeysNotInMetadata.isNotEmpty()) { val metadataNotPresentErrorText = if (metadataKeysNotInSequences.isNotEmpty()) { - "Metadata file contains ${metadataKeysNotInSequences.size} ids that are not present " + + "Metadata file contains ${metadataKeysNotInSequences.size} FASTA ids that are not present " + "in the sequence file: " + metadataKeysNotInSequences.toList().joinToString(limit = 10) + "; " } else { "" } val sequenceNotPresentErrorText = if (sequenceKeysNotInMetadata.isNotEmpty()) { - "Sequence file contains ${sequenceKeysNotInMetadata.size} ids that are not present " + - "in the metadata file: " + sequenceKeysNotInMetadata.toList().joinToString(limit = 10) + "Sequence file contains ${sequenceKeysNotInMetadata.size} FASTA ids that are not present " + + "in the metadata file: " + + sequenceKeysNotInMetadata.toList().joinToString(limit = 10) } else { "" } diff --git a/backend/src/main/kotlin/org/loculus/backend/service/submission/CompressionService.kt b/backend/src/main/kotlin/org/loculus/backend/service/submission/CompressionService.kt index f9b729375a..c9719082a4 100644 --- a/backend/src/main/kotlin/org/loculus/backend/service/submission/CompressionService.kt +++ b/backend/src/main/kotlin/org/loculus/backend/service/submission/CompressionService.kt @@ -102,6 +102,7 @@ class CompressionService(private val compressionDictService: CompressionDictServ } }, processedData.aminoAcidInsertions, + processedData.sequenceNameToFastaHeaderMap, processedData.files, ) @@ -128,6 +129,7 @@ class CompressionService(private val compressionDictService: CompressionDictServ } }, processedData.aminoAcidInsertions, + processedData.sequenceNameToFastaHeaderMap, processedData.files, ) diff --git a/backend/src/main/kotlin/org/loculus/backend/service/submission/EmptyProcessedDataProvider.kt b/backend/src/main/kotlin/org/loculus/backend/service/submission/EmptyProcessedDataProvider.kt index 959093fe80..f715554f08 100644 --- a/backend/src/main/kotlin/org/loculus/backend/service/submission/EmptyProcessedDataProvider.kt +++ b/backend/src/main/kotlin/org/loculus/backend/service/submission/EmptyProcessedDataProvider.kt @@ -20,6 +20,7 @@ class EmptyProcessedDataProvider(private val backendConfig: BackendConfig) { alignedAminoAcidSequences = referenceGenome.genes.map { it.name }.associateWith { null }, nucleotideInsertions = referenceGenome.nucleotideSequences.map { it.name }.associateWith { emptyList() }, aminoAcidInsertions = referenceGenome.genes.map { it.name }.associateWith { emptyList() }, + sequenceNameToFastaHeaderMap = referenceGenome.nucleotideSequences.map { it.name }.associateWith { "" }, files = null, ) } diff --git a/backend/src/main/kotlin/org/loculus/backend/service/submission/ProcessedSequenceEntryValidator.kt b/backend/src/main/kotlin/org/loculus/backend/service/submission/ProcessedSequenceEntryValidator.kt index 61a69e10e5..4024d5f16d 100644 --- a/backend/src/main/kotlin/org/loculus/backend/service/submission/ProcessedSequenceEntryValidator.kt +++ b/backend/src/main/kotlin/org/loculus/backend/service/submission/ProcessedSequenceEntryValidator.kt @@ -232,6 +232,11 @@ class ProcessedSequenceEntryValidator(private val schema: Schema, private val re "alignedNucleotideSequences", ) + validateNoUnknownSegment( + processedData.sequenceNameToFastaHeaderMap, + "sequenceNameToFastaHeaderMap", + ) + validateNoUnknownSegment( processedData.unalignedNucleotideSequences, "unalignedNucleotideSequences", diff --git a/backend/src/main/kotlin/org/loculus/backend/service/submission/SubmissionDatabaseService.kt b/backend/src/main/kotlin/org/loculus/backend/service/submission/SubmissionDatabaseService.kt index bf64313088..b5442ab685 100644 --- a/backend/src/main/kotlin/org/loculus/backend/service/submission/SubmissionDatabaseService.kt +++ b/backend/src/main/kotlin/org/loculus/backend/service/submission/SubmissionDatabaseService.kt @@ -457,6 +457,7 @@ class SubmissionDatabaseService( aminoAcidInsertions = processedData.aminoAcidInsertions.mapValues { (_, it) -> it.map { insertion -> insertion.copy(sequence = insertion.sequence.uppercase(Locale.US)) } }, + sequenceNameToFastaHeaderMap = processedData.sequenceNameToFastaHeaderMap, ) private fun validateExternalMetadata( @@ -1225,7 +1226,7 @@ class SubmissionDatabaseService( .fetchSize(streamBatchSize) .asSequence() .map { - // Revoked sequences have no original metdadata, hence null can happen + // Revoked sequences have no original metadata, hence null can happen @Suppress("USELESS_ELVIS") val metadata = it[originalMetadata] ?: null val selectedMetadata = fields?.associateWith { field -> metadata?.get(field) } diff --git a/backend/src/main/kotlin/org/loculus/backend/service/submission/UploadDatabaseService.kt b/backend/src/main/kotlin/org/loculus/backend/service/submission/UploadDatabaseService.kt index f3f6514219..fc2293666b 100644 --- a/backend/src/main/kotlin/org/loculus/backend/service/submission/UploadDatabaseService.kt +++ b/backend/src/main/kotlin/org/loculus/backend/service/submission/UploadDatabaseService.kt @@ -19,11 +19,13 @@ import org.loculus.backend.api.SubmissionIdMapping import org.loculus.backend.auth.AuthenticatedUser import org.loculus.backend.controller.UnprocessableEntityException import org.loculus.backend.log.AuditLogger +import org.loculus.backend.model.FastaId import org.loculus.backend.model.SubmissionId import org.loculus.backend.model.SubmissionParams import org.loculus.backend.service.GenerateAccessionFromNumberService import org.loculus.backend.service.datauseterms.DataUseTermsDatabaseService import org.loculus.backend.service.submission.MetadataUploadAuxTable.accessionColumn +import org.loculus.backend.service.submission.MetadataUploadAuxTable.fastaIdsColumn import org.loculus.backend.service.submission.MetadataUploadAuxTable.filesColumn import org.loculus.backend.service.submission.MetadataUploadAuxTable.groupIdColumn import org.loculus.backend.service.submission.MetadataUploadAuxTable.metadataColumn @@ -33,13 +35,11 @@ import org.loculus.backend.service.submission.MetadataUploadAuxTable.submitterCo import org.loculus.backend.service.submission.MetadataUploadAuxTable.uploadIdColumn import org.loculus.backend.service.submission.MetadataUploadAuxTable.uploadedAtColumn import org.loculus.backend.service.submission.SequenceUploadAuxTable.compressedSequenceDataColumn -import org.loculus.backend.service.submission.SequenceUploadAuxTable.segmentNameColumn -import org.loculus.backend.service.submission.SequenceUploadAuxTable.sequenceSubmissionIdColumn +import org.loculus.backend.service.submission.SequenceUploadAuxTable.fastaIdColumn import org.loculus.backend.service.submission.SequenceUploadAuxTable.sequenceUploadIdColumn import org.loculus.backend.utils.DatabaseConstants import org.loculus.backend.utils.FastaEntry import org.loculus.backend.utils.MetadataEntry -import org.loculus.backend.utils.ParseFastaHeader import org.loculus.backend.utils.RevisionEntry import org.loculus.backend.utils.chunkedForDatabase import org.loculus.backend.utils.getNextSequenceNumbers @@ -57,7 +57,6 @@ private const val METADATA_BATCH_SIZE = DatabaseConstants.POSTGRESQL_PARAMETER_L @Service @Transactional class UploadDatabaseService( - private val parseFastaHeader: ParseFastaHeader, private val compressor: CompressionService, private val accessionPreconditionValidator: AccessionPreconditionValidator, private val dataUseTermsDatabaseService: DataUseTermsDatabaseService, @@ -80,6 +79,7 @@ class UploadDatabaseService( this[groupIdColumn] = groupId this[uploadedAtColumn] = uploadedAt this[submissionIdColumn] = it.submissionId + this[fastaIdsColumn] = it.fastaIds this[metadataColumn] = it.metadata this[filesColumn] = files?.get(it.submissionId) this[organismColumn] = submittedOrganism.name @@ -118,6 +118,7 @@ class UploadDatabaseService( this[submitterColumn] = authenticatedUser.username this[uploadedAtColumn] = uploadedAt this[submissionIdColumn] = it.submissionId + this[fastaIdsColumn] = it.fastaIds this[metadataColumn] = it.metadata this[filesColumn] = files?.get(it.submissionId) this[organismColumn] = submittedOrganism.name @@ -144,9 +145,7 @@ class UploadDatabaseService( uploadedSequencesBatch.chunkedForDatabase( { batch -> SequenceUploadAuxTable.batchInsert(batch) { - val (submissionId, segmentName) = parseFastaHeader.parse(it.sampleName, submittedOrganism) - this[sequenceSubmissionIdColumn] = submissionId - this[segmentNameColumn] = segmentName + this[fastaIdColumn] = it.fastaId this[sequenceUploadIdColumn] = uploadId this[compressedSequenceDataColumn] = compressor.compressOriginalSequence( it.sequence, @@ -167,14 +166,22 @@ class UploadDatabaseService( .where { uploadIdColumn eq uploadId } .map { it[submissionIdColumn] } + fun getFastaIdsForMetadata(uploadId: String): List> = MetadataUploadAuxTable + .select( + uploadIdColumn, + fastaIdsColumn, + ) + .where { uploadIdColumn eq uploadId } + .map { it[fastaIdsColumn] ?: emptyList() } + fun getSequenceUploadSubmissionIds(uploadId: String): List = SequenceUploadAuxTable .select( sequenceUploadIdColumn, - sequenceSubmissionIdColumn, + fastaIdColumn, ) .where { sequenceUploadIdColumn eq uploadId } .map { - it[sequenceSubmissionIdColumn] + it[fastaIdColumn] } fun deleteAuxTableEntriesOlderThan(thresholdDateTime: LocalDateTime): Int { @@ -204,39 +211,27 @@ class UploadDatabaseService( original_data ) SELECT - metadata_upload_aux_table.accession, - metadata_upload_aux_table.version, - metadata_upload_aux_table.organism, - metadata_upload_aux_table.submission_id, - metadata_upload_aux_table.submitter, - metadata_upload_aux_table.group_id, - metadata_upload_aux_table.uploaded_at, + m.accession, + m.version, + m.organism, + m.submission_id, + m.submitter, + m.group_id, + m.uploaded_at, jsonb_build_object( - 'metadata', metadata_upload_aux_table.metadata, - 'files', metadata_upload_aux_table.files, - 'unalignedNucleotideSequences', - COALESCE( - jsonb_object_agg( - sequence_upload_aux_table.segment_name, - sequence_upload_aux_table.compressed_sequence_data::jsonb - ) FILTER (WHERE sequence_upload_aux_table.segment_name IS NOT NULL), - '{}'::jsonb - ) + 'metadata', m.metadata, + 'files', m.files, + 'unalignedNucleotideSequences', + COALESCE(x.seq_map, '{}'::jsonb) ) - FROM - metadata_upload_aux_table - LEFT JOIN - sequence_upload_aux_table - ON metadata_upload_aux_table.upload_id = sequence_upload_aux_table.upload_id - AND metadata_upload_aux_table.submission_id = sequence_upload_aux_table.submission_id - WHERE metadata_upload_aux_table.upload_id = ? - GROUP BY - metadata_upload_aux_table.upload_id, - metadata_upload_aux_table.organism, - metadata_upload_aux_table.submission_id, - metadata_upload_aux_table.submitter, - metadata_upload_aux_table.group_id, - metadata_upload_aux_table.uploaded_at + FROM metadata_upload_aux_table AS m + LEFT JOIN LATERAL ( + SELECT jsonb_object_agg(s.fasta_id, s.compressed_sequence_data::jsonb) AS seq_map + FROM sequence_upload_aux_table AS s + WHERE s.upload_id = m.upload_id + AND jsonb_exists(COALESCE(m.fasta_ids, '[]'::jsonb), s.fasta_id) + ) AS x ON TRUE + WHERE m.upload_id = ? RETURNING accession, version, submission_id; """.trimIndent() val insertionResult = exec( diff --git a/backend/src/main/kotlin/org/loculus/backend/service/submission/dbtables/MetadataUploadAuxTable.kt b/backend/src/main/kotlin/org/loculus/backend/service/submission/dbtables/MetadataUploadAuxTable.kt index 4293131fb8..b4a234b037 100644 --- a/backend/src/main/kotlin/org/loculus/backend/service/submission/dbtables/MetadataUploadAuxTable.kt +++ b/backend/src/main/kotlin/org/loculus/backend/service/submission/dbtables/MetadataUploadAuxTable.kt @@ -13,6 +13,7 @@ object MetadataUploadAuxTable : Table(METADATA_UPLOAD_AUX_TABLE_NAME) { val uploadIdColumn = varchar("upload_id", 255) val organismColumn = varchar("organism", 255) val submissionIdColumn = varchar("submission_id", 255) + val fastaIdsColumn = jacksonSerializableJsonb>("fasta_ids").nullable() val submitterColumn = varchar("submitter", 255) val groupIdColumn = integer("group_id").nullable() val uploadedAtColumn = datetime("uploaded_at") diff --git a/backend/src/main/kotlin/org/loculus/backend/service/submission/dbtables/SequenceUploadAuxTable.kt b/backend/src/main/kotlin/org/loculus/backend/service/submission/dbtables/SequenceUploadAuxTable.kt index 9f931ac91b..049ffe4cf0 100644 --- a/backend/src/main/kotlin/org/loculus/backend/service/submission/dbtables/SequenceUploadAuxTable.kt +++ b/backend/src/main/kotlin/org/loculus/backend/service/submission/dbtables/SequenceUploadAuxTable.kt @@ -7,9 +7,8 @@ const val SEQUENCE_UPLOAD_AUX_TABLE_NAME = "sequence_upload_aux_table" object SequenceUploadAuxTable : Table(SEQUENCE_UPLOAD_AUX_TABLE_NAME) { val sequenceUploadIdColumn = varchar("upload_id", 255) - val sequenceSubmissionIdColumn = varchar("submission_id", 255) - val segmentNameColumn = varchar("segment_name", 255) + val fastaIdColumn = varchar("fasta_id", 255) val compressedSequenceDataColumn = jacksonSerializableJsonb("compressed_sequence_data") - override val primaryKey = PrimaryKey(sequenceUploadIdColumn, sequenceSubmissionIdColumn, segmentNameColumn) + override val primaryKey = PrimaryKey(sequenceUploadIdColumn, fastaIdColumn) } diff --git a/backend/src/main/kotlin/org/loculus/backend/utils/FastaReader.kt b/backend/src/main/kotlin/org/loculus/backend/utils/FastaReader.kt index d8f673c554..6c27c5ba1e 100644 --- a/backend/src/main/kotlin/org/loculus/backend/utils/FastaReader.kt +++ b/backend/src/main/kotlin/org/loculus/backend/utils/FastaReader.kt @@ -5,7 +5,7 @@ import java.io.BufferedReader import java.io.InputStream import java.io.InputStreamReader -data class FastaEntry(val sampleName: String, val sequence: String) +data class FastaEntry(val fastaId: String, val sequence: String) class FastaReader(inputStream: InputStream) : Iterator, @@ -28,7 +28,7 @@ class FastaReader(inputStream: InputStream) : } private fun read() { - var sampleName: String? = null + var fastaId: String? = null val sequence = StringBuilder() while (true) { if (nextLine == null) { @@ -39,22 +39,24 @@ class FastaReader(inputStream: InputStream) : continue } if (nextLine!!.startsWith(">")) { - if (sampleName != null) { + if (fastaId != null) { break } - sampleName = nextLine!!.substring(1).split("\\s+".toRegex())[0] + fastaId = nextLine!!.substring(1).split("\\s+".toRegex())[0] } else { sequence.append(nextLine) } nextLine = reader.readLine() } - nextEntry = if (sampleName == null) { + nextEntry = if (fastaId == null) { null } else { if (sequence.isEmpty()) { - throw UnprocessableEntityException("No sequence data given for sample $sampleName.") + throw UnprocessableEntityException( + "FASTA sequence with id `$fastaId` is empty (does not contain at least a single nucleotide).", + ) } - FastaEntry(sampleName, sequence.toString()) + FastaEntry(fastaId, sequence.toString()) } } diff --git a/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt b/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt index e99ee99033..36a4938596 100644 --- a/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt +++ b/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt @@ -4,13 +4,19 @@ import org.apache.commons.csv.CSVException import org.apache.commons.csv.CSVFormat import org.loculus.backend.controller.UnprocessableEntityException import org.loculus.backend.model.ACCESSION_HEADER -import org.loculus.backend.model.HEADER_TO_CONNECT_METADATA_AND_SEQUENCES -import org.loculus.backend.model.HEADER_TO_CONNECT_METADATA_AND_SEQUENCES_ALTERNATE_FOR_BACKCOMPAT +import org.loculus.backend.model.FASTA_ID_HEADER +import org.loculus.backend.model.FastaId +import org.loculus.backend.model.METADATA_ID_HEADER +import org.loculus.backend.model.METADATA_ID_HEADER_ALTERNATE_FOR_BACKCOMPAT import org.loculus.backend.model.SubmissionId import java.io.InputStream import java.io.InputStreamReader -data class MetadataEntry(val submissionId: SubmissionId, val metadata: Map) +data class MetadataEntry( + val submissionId: SubmissionId, + val metadata: Map, + val fastaIds: List? = null, +) private fun invalidTsvFormatException(originalException: Exception) = UnprocessableEntityException( "The metadata file is not a valid TSV file. Common causes include: fields not separated by tabs, " + @@ -20,23 +26,33 @@ private fun invalidTsvFormatException(originalException: Exception) = Unprocessa fun findAndValidateSubmissionIdHeader(headerNames: List): String { val submissionIdHeaders = listOf( - HEADER_TO_CONNECT_METADATA_AND_SEQUENCES, - HEADER_TO_CONNECT_METADATA_AND_SEQUENCES_ALTERNATE_FOR_BACKCOMPAT, + METADATA_ID_HEADER, + METADATA_ID_HEADER_ALTERNATE_FOR_BACKCOMPAT, ).filter { headerNames.contains(it) } when { submissionIdHeaders.isEmpty() -> throw UnprocessableEntityException( - "The metadata file does not contain either header '$HEADER_TO_CONNECT_METADATA_AND_SEQUENCES' or '$HEADER_TO_CONNECT_METADATA_AND_SEQUENCES_ALTERNATE_FOR_BACKCOMPAT'", + "The metadata file does not contain either header '$METADATA_ID_HEADER' or '$METADATA_ID_HEADER_ALTERNATE_FOR_BACKCOMPAT'", ) submissionIdHeaders.size > 1 -> throw UnprocessableEntityException( - "The metadata file contains both '$HEADER_TO_CONNECT_METADATA_AND_SEQUENCES' and '$HEADER_TO_CONNECT_METADATA_AND_SEQUENCES_ALTERNATE_FOR_BACKCOMPAT'. Only one is allowed.", + "The metadata file contains both '$METADATA_ID_HEADER' and '$METADATA_ID_HEADER_ALTERNATE_FOR_BACKCOMPAT'. Only one is allowed.", ) } return submissionIdHeaders.first() } -fun metadataEntryStreamAsSequence(metadataInputStream: InputStream): Sequence { +fun determineFastaIdColumnName(metadataHeaderNames: List, submissionIdHeader: String): String { + if (!metadataHeaderNames.contains(FASTA_ID_HEADER)) { + return submissionIdHeader + } + return FASTA_ID_HEADER +} + +fun metadataEntryStreamAsSequence( + metadataInputStream: InputStream, + addFastaIds: Boolean = true, +): Sequence { val csvParser = try { CSVFormat.TDF.builder().setHeader().setSkipHeaderRecord(true).get() .parse(InputStreamReader(metadataInputStream)) @@ -46,6 +62,7 @@ fun metadataEntryStreamAsSequence(metadataInputStream: InputStream): Sequence? = null + + if (addFastaIds) { + val fastaId = record[fastaIdColumn] + if (fastaId.isNullOrEmpty()) { + throw UnprocessableEntityException( + "In metadata file: record #$recordNumber: column `$fastaIdColumn` is empty. This is invalid. Full record: $record", + ) + } + + fastaIds = fastaId.split(Regex("[ ,]+")) + .map { it.trim() } + .filter { it.isNotEmpty() } + } + val metadata = record.toMap().filterKeys { it != submissionIdHeader } - val entry = MetadataEntry(submissionId, metadata) + val entry = MetadataEntry(submissionId, metadata, fastaIds) if (entry.metadata.isEmpty()) { throw UnprocessableEntityException( - "Record #$recordNumber in the metadata file contains no metadata columns: $entry", + "In metadata file: record #$recordNumber contains no metadata. This is invalid. Full record: $entry", ) } @@ -87,9 +119,14 @@ fun metadataEntryStreamAsSequence(metadataInputStream: InputStream): Sequence) +data class RevisionEntry( + val submissionId: SubmissionId, + val accession: Accession, + val metadata: Map, + val fastaIds: List? = null, +) -fun revisionEntryStreamAsSequence(metadataInputStream: InputStream): Sequence { +fun revisionEntryStreamAsSequence(metadataInputStream: InputStream, addFastaIds: Boolean): Sequence { val csvParser = try { CSVFormat.TDF.builder().setHeader().setSkipHeaderRecord(true).get() .parse(InputStreamReader(metadataInputStream)) @@ -99,6 +136,7 @@ fun revisionEntryStreamAsSequence(metadataInputStream: InputStream): Sequence? = null + + if (addFastaIds) { + val fastaId = record[fastaIdHeader] + if (fastaId.isNullOrEmpty()) { + throw UnprocessableEntityException( + "A row in metadata file contains no $fastaIdHeader: $record", + ) + } + + fastaIds = fastaId.split(Regex("[ ,]+")) + .map { it.trim() } + .filter { it.isNotEmpty() } + } + val metadata = record.toMap().filterKeys { it != submissionIdHeader && it != ACCESSION_HEADER } - val entry = RevisionEntry(submissionId, accession, metadata) + val entry = RevisionEntry(submissionId, accession, metadata, fastaIds) if (entry.metadata.isEmpty()) { throw UnprocessableEntityException( diff --git a/backend/src/main/kotlin/org/loculus/backend/utils/ParseFastaHeader.kt b/backend/src/main/kotlin/org/loculus/backend/utils/ParseFastaHeader.kt deleted file mode 100644 index 4815e61e2b..0000000000 --- a/backend/src/main/kotlin/org/loculus/backend/utils/ParseFastaHeader.kt +++ /dev/null @@ -1,40 +0,0 @@ -package org.loculus.backend.utils - -import org.loculus.backend.api.Organism -import org.loculus.backend.config.BackendConfig -import org.loculus.backend.controller.BadRequestException -import org.loculus.backend.model.HEADER_TO_CONNECT_METADATA_AND_SEQUENCES -import org.loculus.backend.model.SegmentName -import org.loculus.backend.model.SubmissionId -import org.springframework.stereotype.Service - -@Service -class ParseFastaHeader(private val backendConfig: BackendConfig) { - fun parse(submissionId: String, organism: Organism): Pair { - val referenceGenome = backendConfig.getInstanceConfig(organism).referenceGenome - - if (referenceGenome.nucleotideSequences.size == 1) { - return Pair(submissionId, "main") - } - - val validSegmentIds = referenceGenome.nucleotideSequences.map { it.name } - - val lastDelimiter = submissionId.lastIndexOf("_") - if (lastDelimiter == -1) { - throw BadRequestException( - "The FASTA header $submissionId does not contain the segment name. Please provide the" + - " segment name in the format <$HEADER_TO_CONNECT_METADATA_AND_SEQUENCES>_", - ) - } - val isolateId = submissionId.substring(0, lastDelimiter) - val segmentId = submissionId.substring(lastDelimiter + 1) - if (!validSegmentIds.contains(segmentId)) { - throw BadRequestException( - "The FASTA header $submissionId ends with the segment name $segmentId, which is not valid. " + - "Valid segment names: ${validSegmentIds.joinToString(", ")}", - ) - } - - return Pair(isolateId, segmentId) - } -} diff --git a/backend/src/main/resources/db/migration/V1.22__refactor_aux_tables.sql b/backend/src/main/resources/db/migration/V1.22__refactor_aux_tables.sql new file mode 100644 index 0000000000..4e30ddc7fd --- /dev/null +++ b/backend/src/main/resources/db/migration/V1.22__refactor_aux_tables.sql @@ -0,0 +1,12 @@ +ALTER TABLE metadata_upload_aux_table + ADD COLUMN fasta_ids jsonb DEFAULT '[]'::jsonb; + +ALTER TABLE sequence_upload_aux_table + DROP CONSTRAINT sequence_upload_aux_table_pkey, + DROP COLUMN submission_id, + ADD COLUMN fasta_id text NOT NULL, + DROP COLUMN segment_name, + ADD CONSTRAINT sequence_upload_aux_table_pkey PRIMARY KEY (upload_id, fasta_id); + +CREATE INDEX ON metadata_upload_aux_table + USING GIN (fasta_ids jsonb_path_ops); \ No newline at end of file diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/PreparedOriginalData.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/PreparedOriginalData.kt index 20f05380bc..525cab15b0 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/PreparedOriginalData.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/PreparedOriginalData.kt @@ -11,7 +11,7 @@ val defaultOriginalData = OriginalData( "country" to "Switzerland", "division" to "Bern", ), - mapOf("main" to "ACTG"), + mapOf("custom0" to "ACTG"), ) val emptyOriginalData = OriginalData(metadata = emptyMap(), unalignedNucleotideSequences = emptyMap()) diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/PreparedProcessedData.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/PreparedProcessedData.kt index 9a874a5ab5..d5523ac3af 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/PreparedProcessedData.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/PreparedProcessedData.kt @@ -62,6 +62,7 @@ val defaultProcessedData = ProcessedData( Insertion(123, "RN"), ), ), + sequenceNameToFastaHeaderMap = mapOf(MAIN_SEGMENT to "header"), files = null, ) @@ -101,6 +102,7 @@ val defaultProcessedDataMultiSegmented = ProcessedData( Insertion(123, "RN"), ), ), + sequenceNameToFastaHeaderMap = mapOf("notOnlySegment" to "header1", "secondSegment" to "header2"), files = null, ) @@ -117,6 +119,7 @@ val defaultProcessedDataWithoutSequences = ProcessedData( nucleotideInsertions = emptyMap(), alignedAminoAcidSequences = emptyMap(), aminoAcidInsertions = emptyMap(), + sequenceNameToFastaHeaderMap = emptyMap(), files = null, ) diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/ReviseEndpointTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/ReviseEndpointTest.kt index 20261bff02..e6b5aaa6b6 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/ReviseEndpointTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/ReviseEndpointTest.kt @@ -130,6 +130,41 @@ class ReviseEndpointTest( ) } + @Test + fun `GIVEN revision of multi-segmented entries with status 'APPROVED_FOR_RELEASE' THEN successful`() { + val accessions = convenienceClient.prepareDataTo(APPROVED_FOR_RELEASE).map { it.accession } + + client.reviseSequenceEntries( + DefaultFiles.getRevisedMultiSegmentedMetadataFile(accessions), + DefaultFiles.sequencesFileMultiSegmented, + ) + .andExpect(status().isOk) + .andExpect(content().contentType(APPLICATION_JSON_VALUE)) + .andExpect(jsonPath("\$.length()").value(DefaultFiles.NUMBER_OF_SEQUENCES)) + .andExpect(jsonPath("\$[0].submissionId").value("custom0")) + .andExpect(jsonPath("\$[0].accession").value(accessions.first())) + .andExpect(jsonPath("\$[0].version").value(2)) + + convenienceClient.getSequenceEntry(accession = accessions.first(), version = 2) + .assertStatusIs(RECEIVED) + convenienceClient.getSequenceEntry(accession = accessions.first(), version = 1) + .assertStatusIs(APPROVED_FOR_RELEASE) + + val result = client.extractUnprocessedData(DefaultFiles.NUMBER_OF_SEQUENCES) + val responseBody = result.expectNdjsonAndGetContent() + assertThat(responseBody, hasSize(10)) + + assertThat( + responseBody, + hasItem( + allOf( + hasProperty("accession", `is`(accessions.first())), + hasProperty("version", `is`(2L)), + ), + ), + ) + } + @Test fun `WHEN submitting revised data with non-existing accessions THEN throws an unprocessableEntity error`() { val accessions = convenienceClient.prepareDataTo(APPROVED_FOR_RELEASE).map { @@ -187,7 +222,7 @@ class ReviseEndpointTest( } client.reviseSequenceEntries( - DefaultFiles.getRevisedMetadataFile(accessions), + DefaultFiles.getRevisedMultiSegmentedMetadataFile(accessions), DefaultFiles.sequencesFileMultiSegmented, organism = OTHER_ORGANISM, ) @@ -588,7 +623,7 @@ class ReviseEndpointTest( ), status().isUnprocessableEntity, "Unprocessable Entity", - "Sequence file contains 1 ids that are not present in the metadata file: notInMetadata", + "Sequence file contains 1 FASTA ids that are not present in the metadata file: notInMetadata", ), Arguments.of( "sequence file misses submissionIds", @@ -607,7 +642,7 @@ class ReviseEndpointTest( ), status().isUnprocessableEntity, "Unprocessable Entity", - "Metadata file contains 1 ids that are not present in the sequence file: notInSequences", + "Metadata file contains 1 FASTA ids that are not present in the sequence file: notInSequences", ), Arguments.of( "metadata file misses accession header", diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmissionConvenienceClient.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmissionConvenienceClient.kt index 5c84f57418..9e090f3521 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmissionConvenienceClient.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmissionConvenienceClient.kt @@ -114,7 +114,11 @@ class SubmissionConvenienceClient( } val submit = client.submit( - DefaultFiles.metadataFile, + if (isMultiSegmented) { + DefaultFiles.multiSegmentedMetadataFile + } else { + DefaultFiles.metadataFile + }, if (doesNotAllowConsensusSequenceFile) { null } else if (isMultiSegmented) { diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointSingleSegmentedTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointSingleSegmentedTest.kt deleted file mode 100644 index 45c0595cdc..0000000000 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointSingleSegmentedTest.kt +++ /dev/null @@ -1,90 +0,0 @@ -package org.loculus.backend.controller.submission - -import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers.containsString -import org.hamcrest.Matchers.hasEntry -import org.junit.jupiter.api.Test -import org.loculus.backend.config.BackendConfig -import org.loculus.backend.config.BackendSpringProperty -import org.loculus.backend.controller.EndpointTest -import org.loculus.backend.controller.SINGLE_SEGMENTED_REFERENCE_GENOME -import org.loculus.backend.controller.groupmanagement.GroupManagementControllerClient -import org.loculus.backend.controller.groupmanagement.andGetGroupId -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.http.MediaType.APPLICATION_JSON_VALUE -import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content -import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath -import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status - -private const val DEFAULT_SEQUENCE_NAME = "main" - -@EndpointTest( - properties = ["${BackendSpringProperty.BACKEND_CONFIG_PATH}=$SINGLE_SEGMENTED_REFERENCE_GENOME"], -) -class SubmitEndpointSingleSegmentedTest( - @Autowired val submissionControllerClient: SubmissionControllerClient, - @Autowired val convenienceClient: SubmissionConvenienceClient, - @Autowired val backendConfig: BackendConfig, - @Autowired private val groupManagementClient: GroupManagementControllerClient, -) { - @Test - fun `GIVEN valid input data without segment name THEN data is accepted and shows segment name 'main'`() { - val groupId = groupManagementClient.createNewGroup().andGetGroupId() - - submissionControllerClient.submit( - SubmitFiles.metadataFileWith( - content = """ - submissionId firstColumn - header1 someValue - header2 someValue - """.trimIndent(), - ), - SubmitFiles.sequenceFileWith( - content = """ - >header1 - AC - >header2 - GT - """.trimIndent(), - ), - groupId = groupId, - ) - .andExpect(status().isOk) - .andExpect(content().contentType(APPLICATION_JSON_VALUE)) - .andExpect(jsonPath("\$.length()").value(2)) - .andExpect(jsonPath("\$[0].submissionId").value("header1")) - .andExpect(jsonPath("\$[0].accession", containsString(backendConfig.accessionPrefix))) - .andExpect(jsonPath("\$[0].version").value(1)) - - val unalignedNucleotideSequences = convenienceClient.extractUnprocessedData()[0] - .data - .unalignedNucleotideSequences - - assertThat(unalignedNucleotideSequences, hasEntry(DEFAULT_SEQUENCE_NAME, "AC")) - } - - @Test - fun `GIVEN input data with explicit default segment name THEN data is rejected`() { - val groupId = groupManagementClient.createNewGroup().andGetGroupId() - val expectedDetail = "Metadata file contains 1 ids that are not present in the sequence file: header1" - - submissionControllerClient.submit( - SubmitFiles.metadataFileWith( - content = """ - submissionId firstColumn - header1 someValue - """.trimIndent(), - ), - SubmitFiles.sequenceFileWith( - content = """ - >header1_$DEFAULT_SEQUENCE_NAME - AC - """.trimIndent(), - ), - groupId = groupId, - ) - .andExpect(status().isUnprocessableEntity) - .andExpect(jsonPath("\$.title").value("Unprocessable Entity")) - .andExpect(jsonPath("\$.detail", containsString(expectedDetail))) - } -} diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointTest.kt index b4be1ef2a5..8af3ea51ea 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointTest.kt @@ -5,7 +5,9 @@ import kotlinx.datetime.DateTimeUnit.Companion.YEAR import kotlinx.datetime.minus import kotlinx.datetime.plus import kotlinx.datetime.toLocalDateTime +import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.containsString +import org.hamcrest.Matchers.hasEntry import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest @@ -33,6 +35,7 @@ import org.loculus.backend.utils.DateProvider import org.springframework.beans.factory.annotation.Autowired import org.springframework.http.MediaType import org.springframework.http.MediaType.APPLICATION_JSON +import org.springframework.http.MediaType.APPLICATION_JSON_VALUE import org.springframework.http.MediaType.APPLICATION_PROBLEM_JSON import org.springframework.mock.web.MockMultipartFile import org.springframework.test.web.servlet.ResultMatcher @@ -45,6 +48,7 @@ import kotlin.time.Clock @EndpointTest class SubmitEndpointTest( @Autowired val submissionControllerClient: SubmissionControllerClient, + @Autowired val convenienceClient: SubmissionConvenienceClient, @Autowired val backendConfig: BackendConfig, @Autowired val groupManagementClient: GroupManagementControllerClient, ) { @@ -140,7 +144,7 @@ class SubmitEndpointTest( @Test fun `GIVEN valid input multi segment data THEN returns mapping of provided custom ids to generated ids`() { submissionControllerClient.submit( - DefaultFiles.metadataFile, + DefaultFiles.multiSegmentedMetadataFile, DefaultFiles.sequencesFileMultiSegmented, organism = OTHER_ORGANISM, groupId = groupId, @@ -154,104 +158,126 @@ class SubmitEndpointTest( } @Test - fun `GIVEN submission without data use terms THEN returns an error`() { - submissionControllerClient.submitWithoutDataUseTerms( - DefaultFiles.metadataFile, - DefaultFiles.sequencesFileMultiSegmented, - organism = OTHER_ORGANISM, - groupId = groupId, - ) - .andExpect(status().isBadRequest) - } + fun `GIVEN valid input data THEN data is accepted and shows fastaID`() { + val groupId = groupManagementClient.createNewGroup().andGetGroupId() - @Test - fun `GIVEN fasta data with unknown segment THEN data is not accepted`() { submissionControllerClient.submit( SubmitFiles.metadataFileWith( content = """ submissionId firstColumn - commonHeader someValue + header1 someValue + header2 someValue """.trimIndent(), ), SubmitFiles.sequenceFileWith( content = """ - >commonHeader_nonExistingSegmentName + >header1 AC + >header2 + GT """.trimIndent(), ), - organism = OTHER_ORGANISM, groupId = groupId, ) - .andExpect(status().isBadRequest) - .andExpect(content().contentType(APPLICATION_PROBLEM_JSON)) - .andExpect( - jsonPath( - "\$.detail", - ).value( - "The FASTA header commonHeader_nonExistingSegmentName ends with the segment name nonExistingSegmentName, which is not valid. Valid segment names: notOnlySegment, secondSegment", - ), - ) + .andExpect(status().isOk) + .andExpect(content().contentType(APPLICATION_JSON_VALUE)) + .andExpect(jsonPath("\$.length()").value(2)) + .andExpect(jsonPath("\$[0].submissionId").value("header1")) + .andExpect(jsonPath("\$[0].accession", containsString(backendConfig.accessionPrefix))) + .andExpect(jsonPath("\$[0].version").value(1)) + + val unalignedNucleotideSequences = convenienceClient.extractUnprocessedData()[0] + .data + .unalignedNucleotideSequences + + assertThat(unalignedNucleotideSequences, hasEntry("header1", "AC")) } @Test - fun `GIVEN fasta data with empty segment THEN data is not accepted`() { + fun `GIVEN valid input multi segment data THEN data is accepted and originalData shows fastaID`() { + val groupId = groupManagementClient.createNewGroup().andGetGroupId() + submissionControllerClient.submit( SubmitFiles.metadataFileWith( content = """ - submissionId firstColumn - commonHeader someValue + submissionId firstColumn fastaId + header1 someValue header1_seg1 header1_seg2 + header2 someValue fasta_header2_seg1 """.trimIndent(), ), SubmitFiles.sequenceFileWith( content = """ - >commonHeader_notOnlySegment + >header1_seg1 AC - >commonHeader_secondSegment + >header1_seg2 + GT + >fasta_header2_seg1 + TA """.trimIndent(), ), organism = OTHER_ORGANISM, groupId = groupId, ) - .andExpect(status().isUnprocessableEntity()) - .andExpect(content().contentType(APPLICATION_PROBLEM_JSON)) - .andExpect( - jsonPath( - "\$.detail", - ).value( - "No sequence data given for sample commonHeader_secondSegment.", - ), - ) + .andExpect(status().isOk) + .andExpect(content().contentType(APPLICATION_JSON_VALUE)) + .andExpect(jsonPath("\$.length()").value(2)) + .andExpect(jsonPath("\$[0].submissionId").value("header1")) + .andExpect(jsonPath("\$[0].accession", containsString(backendConfig.accessionPrefix))) + .andExpect(jsonPath("\$[0].version").value(1)) + + val unalignedNucleotideSequences = convenienceClient.extractUnprocessedData(organism = OTHER_ORGANISM)[0] + .data + .unalignedNucleotideSequences + + assertThat(unalignedNucleotideSequences, hasEntry("header1_seg1", "AC")) } @Test - fun `GIVEN fasta data with whitespace-only segment THEN data is not accepted`() { + fun `GIVEN valid input multi segment data without fastaID THEN data is accepted and originalData shows fastaID`() { + val groupId = groupManagementClient.createNewGroup().andGetGroupId() + submissionControllerClient.submit( SubmitFiles.metadataFileWith( content = """ submissionId firstColumn - commonHeader someValue + header1 someValue + header2 someValue """.trimIndent(), ), SubmitFiles.sequenceFileWith( content = """ - >commonHeader_notOnlySegment + >header1 AC - >commonHeader_secondSegment - + >header2 + TA """.trimIndent(), ), organism = OTHER_ORGANISM, groupId = groupId, ) - .andExpect(status().isUnprocessableEntity()) - .andExpect(content().contentType(APPLICATION_PROBLEM_JSON)) - .andExpect( - jsonPath( - "\$.detail", - ).value( - "No sequence data given for sample commonHeader_secondSegment.", - ), - ) + .andExpect(status().isOk) + .andExpect(content().contentType(APPLICATION_JSON_VALUE)) + .andExpect(jsonPath("\$.length()").value(2)) + .andExpect(jsonPath("\$[0].submissionId").value("header1")) + .andExpect(jsonPath("\$[0].accession", containsString(backendConfig.accessionPrefix))) + .andExpect(jsonPath("\$[0].version").value(1)) + + val unalignedNucleotideSequences = convenienceClient.extractUnprocessedData(organism = OTHER_ORGANISM)[0] + .data + .unalignedNucleotideSequences + + assertThat(unalignedNucleotideSequences, hasEntry("header1", "AC")) + } + + @Test + fun `GIVEN submission without data use terms THEN returns an error`() { + submissionControllerClient.submitWithoutDataUseTerms( + DefaultFiles.metadataFile, + DefaultFiles.sequencesFileMultiSegmented, + organism = OTHER_ORGANISM, + groupId = groupId, + ) + .andExpect(status().isBadRequest) } @Test @@ -406,9 +432,15 @@ class SubmitEndpointTest( status().isBadRequest, "Bad Request", "${metadataFileTypes.displayName} has wrong extension. Must be " + - ".${metadataFileTypes.validExtensions.joinToString(", .")} for uncompressed submissions or " + ".${ - metadataFileTypes.getCompressedExtensions().filterKeys { it != CompressionAlgorithm.NONE } + metadataFileTypes.validExtensions.joinToString( + ", .", + ) + } for uncompressed submissions or " + + ".${ + metadataFileTypes.getCompressedExtensions().filterKeys { + it != CompressionAlgorithm.NONE + } .flatMap { it.value }.joinToString(", .") } for compressed submissions", DEFAULT_ORGANISM, @@ -421,9 +453,15 @@ class SubmitEndpointTest( status().isBadRequest, "Bad Request", "${sequenceFileTypes.displayName} has wrong extension. Must be " + - ".${sequenceFileTypes.validExtensions.joinToString(", .")} for uncompressed submissions or " + ".${ - sequenceFileTypes.getCompressedExtensions().filterKeys { it != CompressionAlgorithm.NONE } + sequenceFileTypes.validExtensions.joinToString( + ", .", + ) + } for uncompressed submissions or " + + ".${ + sequenceFileTypes.getCompressedExtensions().filterKeys { + it != CompressionAlgorithm.NONE + } .flatMap { it.value }.joinToString(", .") } for compressed submissions", DEFAULT_ORGANISM, @@ -511,7 +549,7 @@ class SubmitEndpointTest( ), status().isUnprocessableEntity, "Unprocessable Entity", - "Sequence file contains 1 ids that are not present in the metadata file: notInMetadata", + "Sequence file contains 1 FASTA ids that are not present in the metadata file: notInMetadata", DEFAULT_ORGANISM, DataUseTerms.Open, ), @@ -532,31 +570,10 @@ class SubmitEndpointTest( ), status().isUnprocessableEntity, "Unprocessable Entity", - "Metadata file contains 1 ids that are not present in the sequence file: notInSequences", + "Metadata file contains 1 FASTA ids that are not present in the sequence file: notInSequences", DEFAULT_ORGANISM, DataUseTerms.Open, ), - Arguments.of( - "FASTA header misses segment name", - SubmitFiles.metadataFileWith( - content = """ - submissionId firstColumn - commonHeader someValue - """.trimIndent(), - ), - SubmitFiles.sequenceFileWith( - content = """ - >commonHeader - AC - """.trimIndent(), - ), - status().isBadRequest, - "Bad Request", - "The FASTA header commonHeader does not contain the segment name. Please provide the segment " + - "name in the format _", - OTHER_ORGANISM, - DataUseTerms.Open, - ), Arguments.of( "restricted use data with until date in the past", DefaultFiles.metadataFile, diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitFiles.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitFiles.kt index ea8beb9c97..71293a39b3 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitFiles.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitFiles.kt @@ -16,9 +16,11 @@ import java.util.zip.ZipEntry import java.util.zip.ZipOutputStream private const val DEFAULT_METADATA_FILE_NAME = "metadata.tsv" +private const val DEFAULT_MULTI_SEGMENTED_METADATA_FILE_NAME = "metadata_multi_segment.tsv" private const val REVISED_METADATA_FILE_NAME = "revised_metadata.tsv" +private const val REVISED_MULTI_SEGMENTED_METADATA_FILE_NAME = "revised_metadata_multi_segment.tsv" private const val DEFAULT_SEQUENCES_FILE_NAME = "sequences.fasta" -private const val DEFAULT_MULTI_SEGMENT_SEQUENCES_FILE_NAME = "sequences_multi_segment.fasta" +private const val DEFAULT_MULTI_SEGMENTED_SEQUENCES_FILE_NAME = "sequences_multi_segment.fasta" object SubmitFiles { @@ -30,6 +32,23 @@ object SubmitFiles { "someOtherAccession\tsomeHeader2\tsomeValue2", ) + fun getRevisedMultiSegmentedMetadataFile(accessions: List): MockMultipartFile { + val fileContent = getFileContent(REVISED_MULTI_SEGMENTED_METADATA_FILE_NAME) + + val lines = fileContent.trim().split("\n").toMutableList() + val headerLine = lines.removeFirst() + + val revisedLines = lines + .map { it.substringAfter('\t') } + .zip(accessions) + .map { (line, accession) -> "$accession\t$line" } + .toMutableList() + + revisedLines.addFirst(headerLine) + + return metadataFileWith(content = revisedLines.joinToString("\n")) + } + fun getRevisedMetadataFile(accessions: List): MockMultipartFile { val fileContent = TestResource(REVISED_METADATA_FILE_NAME).content @@ -55,16 +74,24 @@ object SubmitFiles { } val sequencesFiles = CompressionAlgorithm.entries.associateWith { sequenceFileWith( - content = TestResource(DEFAULT_SEQUENCES_FILE_NAME).content, + content = getFileContent(DEFAULT_SEQUENCES_FILE_NAME), + compression = it, + ) + } + private val metadataFilesMultiSegmented = CompressionAlgorithm.entries.associateWith { + metadataFileWith( + content = getFileContent(DEFAULT_MULTI_SEGMENTED_METADATA_FILE_NAME), compression = it, ) } private val sequencesFilesMultiSegmented = CompressionAlgorithm.entries.associateWith { sequenceFileWith( - content = TestResource(DEFAULT_MULTI_SEGMENT_SEQUENCES_FILE_NAME).content, + content = TestResource(DEFAULT_MULTI_SEGMENTED_SEQUENCES_FILE_NAME).content, compression = it, ) } + val multiSegmentedMetadataFile = metadataFilesMultiSegmented[CompressionAlgorithm.NONE] + ?: error("No multi-segment metadata file") val metadataFile = metadataFiles[CompressionAlgorithm.NONE] ?: error("No metadata file") val sequencesFile = sequencesFiles[CompressionAlgorithm.NONE] ?: error("No sequences file") val sequencesFileMultiSegmented = sequencesFilesMultiSegmented[CompressionAlgorithm.NONE] ?: error( @@ -73,6 +100,12 @@ object SubmitFiles { val submissionIds = List(10) { "custom$it" } const val NUMBER_OF_SEQUENCES = 10 + + private fun getFileContent(file: String): String = String( + this::class.java.classLoader.getResourceAsStream(file)?.readBytes() ?: error( + "$file resource for tests not found", + ), + ) } fun metadataFileWith( diff --git a/backend/src/test/kotlin/org/loculus/backend/service/ProcessedMetadataPostprocessorTest.kt b/backend/src/test/kotlin/org/loculus/backend/service/ProcessedMetadataPostprocessorTest.kt index 2a5d304275..6ecbb08e79 100644 --- a/backend/src/test/kotlin/org/loculus/backend/service/ProcessedMetadataPostprocessorTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/service/ProcessedMetadataPostprocessorTest.kt @@ -43,6 +43,7 @@ class ProcessedMetadataPostprocessorTest( nucleotideInsertions = emptyMap(), alignedAminoAcidSequences = emptyMap(), aminoAcidInsertions = emptyMap(), + sequenceNameToFastaHeaderMap = emptyMap(), files = null, ) diff --git a/backend/src/test/kotlin/org/loculus/backend/service/ProcessedSequencesPostprocessorTest.kt b/backend/src/test/kotlin/org/loculus/backend/service/ProcessedSequencesPostprocessorTest.kt index fcf7873912..51e6db7353 100644 --- a/backend/src/test/kotlin/org/loculus/backend/service/ProcessedSequencesPostprocessorTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/service/ProcessedSequencesPostprocessorTest.kt @@ -91,6 +91,10 @@ class ProcessedSequencesPostprocessorTest( unconfiguredPresentGene to listOf(Insertion(13, "TT")), unconfiguredNullGene to emptyList(), ), + sequenceNameToFastaHeaderMap = mapOf( + "configuredPresentSeg" to "header1", + "unconfiguredPresentSeg" to "header2", + ), files = null, ) diff --git a/backend/src/test/kotlin/org/loculus/backend/utils/EarliestReleaseDateFinderTest.kt b/backend/src/test/kotlin/org/loculus/backend/utils/EarliestReleaseDateFinderTest.kt index 03d4b781da..43e89af3a4 100644 --- a/backend/src/test/kotlin/org/loculus/backend/utils/EarliestReleaseDateFinderTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/utils/EarliestReleaseDateFinderTest.kt @@ -62,6 +62,7 @@ fun row( nucleotideInsertions = emptyMap(), alignedAminoAcidSequences = emptyMap(), aminoAcidInsertions = emptyMap(), + sequenceNameToFastaHeaderMap = emptyMap(), files = null, ), isRevocation = false, diff --git a/backend/src/test/kotlin/org/loculus/backend/utils/FastaReaderTest.kt b/backend/src/test/kotlin/org/loculus/backend/utils/FastaReaderTest.kt index 8af84b38c5..b398e4f866 100644 --- a/backend/src/test/kotlin/org/loculus/backend/utils/FastaReaderTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/utils/FastaReaderTest.kt @@ -19,11 +19,11 @@ class FastaReaderTest { """.trimIndent() val parsed = FastaReader(fasta.byteInputStream()).toList() assert(parsed.size == 3) - assert(parsed[0].sampleName.equals("seq1")) + assert(parsed[0].fastaId.equals("seq1")) assert(parsed[0].sequence.equals("AAATTT")) - assert(parsed[1].sampleName.equals("seq2")) + assert(parsed[1].fastaId.equals("seq2")) assert(parsed[1].sequence.equals("TTTCCC")) - assert(parsed[2].sampleName.equals("seq3")) + assert(parsed[2].fastaId.equals("seq3")) assert(parsed[2].sequence.equals("CCCGGG")) } @@ -34,7 +34,7 @@ class FastaReaderTest { CCCGGG """.trimIndent() val parsed = FastaReader(fasta.byteInputStream()).toList() - assert(parsed[0].sampleName.equals("seq1")) + assert(parsed[0].fastaId.equals("seq1")) } @Test @@ -44,7 +44,7 @@ class FastaReaderTest { CCCGGG """.trimIndent() val parsed = FastaReader(fasta.byteInputStream()).toList() - assert(parsed[0].sampleName.equals("seq1")) + assert(parsed[0].fastaId.equals("seq1")) } @Test diff --git a/backend/src/test/kotlin/org/loculus/backend/utils/MetadataEntryTest.kt b/backend/src/test/kotlin/org/loculus/backend/utils/MetadataEntryTest.kt index 908261ff69..101b87e86c 100644 --- a/backend/src/test/kotlin/org/loculus/backend/utils/MetadataEntryTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/utils/MetadataEntryTest.kt @@ -133,7 +133,7 @@ class RevisionEntryTest { foo${'\t'}ACC123${'\t'}bar """.trimIndent() val inputStream = ByteArrayInputStream(str.toByteArray()) - val entries = revisionEntryStreamAsSequence(inputStream).toList() + val entries = revisionEntryStreamAsSequence(inputStream, true).toList() assert(entries.size == 1) assert(entries[0].submissionId == "foo") assert(entries[0].accession == "ACC123") @@ -147,7 +147,7 @@ class RevisionEntryTest { foo${'\t'}bar """.trimIndent() val inputStream = ByteArrayInputStream(str.toByteArray()) - assertThrows { revisionEntryStreamAsSequence(inputStream).toList() } + assertThrows { revisionEntryStreamAsSequence(inputStream, true).toList() } } @Test @@ -159,7 +159,7 @@ class RevisionEntryTest { "123${'\t'}ACC123${'\t'}2025-01-01${'\t'}Switzerland Homo Sapiens Potter, H;\n" val inputStream = ByteArrayInputStream(str.toByteArray()) val exception = assertThrows { - revisionEntryStreamAsSequence(inputStream).toList() + revisionEntryStreamAsSequence(inputStream, true).toList() } assert(exception.message!!.contains("not a valid TSV file")) assert(exception.message!!.contains("Common causes include")) @@ -180,7 +180,7 @@ class RevisionEntryTest { val inputStream = ByteArrayInputStream(str.toByteArray()) val exception = assertThrows { - revisionEntryStreamAsSequence(inputStream).toList() + revisionEntryStreamAsSequence(inputStream, true).toList() } assert(exception.message!!.contains("not a valid TSV file")) assert(exception.message!!.contains("Common causes include")) @@ -194,7 +194,7 @@ class RevisionEntryTest { """.trimIndent() val inputStream = ByteArrayInputStream(str.toByteArray()) val exception = assertThrows { - revisionEntryStreamAsSequence(inputStream).toList() + revisionEntryStreamAsSequence(inputStream, true).toList() } assert(exception.message!!.contains("Record #1")) assert(exception.message!!.contains("contains no value for")) @@ -208,7 +208,7 @@ class RevisionEntryTest { """.trimIndent() val inputStream = ByteArrayInputStream(str.toByteArray()) val exception = assertThrows { - revisionEntryStreamAsSequence(inputStream).toList() + revisionEntryStreamAsSequence(inputStream, true).toList() } assert(exception.message!!.contains("Record #1")) assert(exception.message!!.contains("accession")) diff --git a/backend/src/test/resources/metadata_multi_segment.tsv b/backend/src/test/resources/metadata_multi_segment.tsv new file mode 100644 index 0000000000..9c56b8cea9 --- /dev/null +++ b/backend/src/test/resources/metadata_multi_segment.tsv @@ -0,0 +1,11 @@ +submissionId date region country division host fastaId +custom0 2020-12-26 Europe Switzerland Bern Homo sapiens custom0_notOnlySegment +custom1 2020-12-15 Europe Switzerland Schaffhausen Homo sapiens custom1_notOnlySegment,custom1_secondSegment +custom2 2020-12-02 Europe Switzerland Bern Homo sapiens custom2_notOnlySegment +custom3 2020-12-25 Europe Switzerland Schaffhausen Homo sapiens custom3_notOnlySegment +custom4 2020-12-03 Europe Switzerland Zürich Homo sapiens custom4_notOnlySegment,custom4_secondSegment +custom5 2020-12-23 Europe Switzerland Basel-Land Homo sapiens custom5_notOnlySegment,custom5_secondSegment +custom6 2020-12-16 Europe Switzerland Aargau Homo sapiens custom6_notOnlySegment +custom7 2020-12-31 Europe Switzerland Sankt Gallen Homo sapiens custom7_notOnlySegment +custom8 2020-12-16 Europe Switzerland Aargau Homo sapiens custom8_secondSegment +custom9 2020-12-01 Europe Switzerland Basel-Stadt Homo sapiens custom9_secondSegment diff --git a/backend/src/test/resources/revised_metadata_multi_segment.tsv b/backend/src/test/resources/revised_metadata_multi_segment.tsv new file mode 100644 index 0000000000..290e5a3a57 --- /dev/null +++ b/backend/src/test/resources/revised_metadata_multi_segment.tsv @@ -0,0 +1,11 @@ +accession submissionId date region country division host fastaId +1 custom0 2020-12-26 Europe Switzerland Bern Homo sapiens custom0_notOnlySegment +2 custom1 2020-12-15 Europe Switzerland Schaffhausen Homo sapiens custom1_notOnlySegment,custom1_secondSegment +3 custom2 2020-12-02 Europe Switzerland Bern Homo sapiens custom2_notOnlySegment +4 custom3 2020-12-25 Europe Switzerland Schaffhausen Homo sapiens custom3_notOnlySegment +5 custom4 2020-12-03 Europe Switzerland Zürich Homo sapiens custom4_notOnlySegment,custom4_secondSegment +6 custom5 2020-12-23 Europe Switzerland Basel-Land Homo sapiens custom5_notOnlySegment,custom5_secondSegment +7 custom6 2020-12-16 Europe Switzerland Aargau Homo sapiens custom6_notOnlySegment +8 custom7 2020-12-31 Europe Switzerland Sankt Gallen Homo sapiens custom7_notOnlySegment +9 custom8 2020-12-16 Europe Switzerland Aargau Homo sapiens custom8_secondSegment +10 custom9 2020-12-01 Europe Switzerland Basel-Stadt Homo sapiens custom9_secondSegment diff --git a/ingest/scripts/heuristic_group_segments.py b/ingest/scripts/heuristic_group_segments.py index f5a04aa0eb..139e84cf9c 100644 --- a/ingest/scripts/heuristic_group_segments.py +++ b/ingest/scripts/heuristic_group_segments.py @@ -211,6 +211,11 @@ def main( if segment in group ] ) + segments_list_str = ", ".join([ + f"{joint_key}_{segment}" + for segment in config.nucleotide_sequences + if segment in group + ]) for segment, accession in group.items(): fasta_id_map[accession] = f"{joint_key}_{segment}" @@ -233,14 +238,16 @@ def main( ) row["id"] = joint_key + row["fastaId"] = segments_list_str # Hash of all metadata fields should be the same if # 1. field is not in keys_to_keep and # 2. field is in keys_to_keep but is "" or None filtered_record = {k: str(v) for k, v in row.items() if v is not None and str(v)} - # rename "id" to "submissionId" for back-compatibility with old hashes + # rename "id" to "submissionId" and ignore fastaId for back-compatibility with old hashes filtered_record["submissionId"] = filtered_record.pop("id") + filtered_record.pop("fastaId", None) row["hash"] = hashlib.md5( json.dumps(filtered_record, sort_keys=True).encode(), usedforsecurity=False diff --git a/ingest/tests/expected_output_cchf/revise_metadata.tsv b/ingest/tests/expected_output_cchf/revise_metadata.tsv index 43fe144139..91adc61cbd 100644 --- a/ingest/tests/expected_output_cchf/revise_metadata.tsv +++ b/ingest/tests/expected_output_cchf/revise_metadata.tsv @@ -1,2 +1,2 @@ -authorAffiliations authors bioprojectAccession biosampleAccession geoLocAdmin1 geoLocCountry hostNameScientific hostTaxonId isLabHost ncbiReleaseDate ncbiSourceDb ncbiVirusName ncbiVirusTaxId sampleCollectionDate specimenCollectorSampleId ncbiUpdateDate_L ncbiUpdateDate_M ncbiUpdateDate_S insdcVersion_L insdcVersion_M insdcVersion_S insdcRawReadsAccession_L insdcRawReadsAccession_M insdcRawReadsAccession_S hash_L hash_M hash_S insdcAccessionBase_L insdcAccessionBase_M insdcAccessionBase_S insdcAccessionFull_L insdcAccessionFull_M insdcAccessionFull_S id hash accession -Public Health England, Research Deryabin, ; Atshabar, B.; Sansyzbaev, Y.; Berezin, V.; Nurmakhanov, T.; Yeskhojayev, O.; Vilkova, A.; Shevtsov, A.; Hewson, R.; Atkinson, B. Sairam district Kazakhstan Hyalomma anatolicum 176092 2016-04-30T00:00:00Z GenBank Orthonairovirus haemorrhagiae 3052518 2015 tick pool #134 2016-04-30T00:00:00Z 1 4f8a46f4b233b3d9f05d5336b8b711aa KX096703 KX096703.1 KX096703.1.S c7d098a014a36e8b8f87c73621b7d6fc LOC_0000VXA +authorAffiliations authors bioprojectAccession biosampleAccession geoLocAdmin1 geoLocCountry hostNameScientific hostTaxonId isLabHost ncbiReleaseDate ncbiSourceDb ncbiVirusName ncbiVirusTaxId sampleCollectionDate specimenCollectorSampleId ncbiUpdateDate_L ncbiUpdateDate_M ncbiUpdateDate_S insdcVersion_L insdcVersion_M insdcVersion_S insdcRawReadsAccession_L insdcRawReadsAccession_M insdcRawReadsAccession_S hash_L hash_M hash_S insdcAccessionBase_L insdcAccessionBase_M insdcAccessionBase_S insdcAccessionFull_L insdcAccessionFull_M insdcAccessionFull_S id hash accession fastaId +Public Health England, Research Deryabin, ; Atshabar, B.; Sansyzbaev, Y.; Berezin, V.; Nurmakhanov, T.; Yeskhojayev, O.; Vilkova, A.; Shevtsov, A.; Hewson, R.; Atkinson, B. Sairam district Kazakhstan Hyalomma anatolicum 176092 2016-04-30T00:00:00Z GenBank Orthonairovirus haemorrhagiae 3052518 2015 tick pool #134 2016-04-30T00:00:00Z 1 4f8a46f4b233b3d9f05d5336b8b711aa KX096703 KX096703.1 KX096703.1.S c7d098a014a36e8b8f87c73621b7d6fc LOC_0000VXA KX096703.1.S_S diff --git a/ingest/tests/expected_output_cchf/submit_metadata.tsv b/ingest/tests/expected_output_cchf/submit_metadata.tsv index 6658fc75e2..8d4736c8cd 100644 --- a/ingest/tests/expected_output_cchf/submit_metadata.tsv +++ b/ingest/tests/expected_output_cchf/submit_metadata.tsv @@ -1,2 +1,2 @@ -authorAffiliations authors bioprojectAccession biosampleAccession geoLocAdmin1 geoLocCountry hostNameScientific hostTaxonId isLabHost ncbiReleaseDate ncbiSourceDb ncbiVirusName ncbiVirusTaxId sampleCollectionDate specimenCollectorSampleId ncbiUpdateDate_L ncbiUpdateDate_M ncbiUpdateDate_S insdcVersion_L insdcVersion_M insdcVersion_S insdcRawReadsAccession_L insdcRawReadsAccession_M insdcRawReadsAccession_S hash_L hash_M hash_S insdcAccessionBase_L insdcAccessionBase_M insdcAccessionBase_S insdcAccessionFull_L insdcAccessionFull_M insdcAccessionFull_S id hash -Chumakov Institute of Poliomyelitis and Viral Encephalitides Lukashev, A. N.; Klimentov, A. S.; Smirnova, S. E.; Dzagurova, T. K.; Drexler, J. F.; Gmyl, A. P. Uganda Homo sapiens 9606 2016-12-07T00:00:00Z GenBank Orthonairovirus haemorrhagiae 3052518 1958 Nakiwogo 2016-12-07T00:00:00Z 2016-12-07T00:00:00Z 1 1 7b10a4e21daa8a2e693958761be17d53 70954bc35782b5592858ac3f1a6bbf89 KX013483 KX013485 KX013483.1 KX013485.1 KX013483.1.L/KX013485.1.S bb3a6d8df47cb2891e7b60030a40c335 +authorAffiliations authors bioprojectAccession biosampleAccession geoLocAdmin1 geoLocCountry hostNameScientific hostTaxonId isLabHost ncbiReleaseDate ncbiSourceDb ncbiVirusName ncbiVirusTaxId sampleCollectionDate specimenCollectorSampleId ncbiUpdateDate_L ncbiUpdateDate_M ncbiUpdateDate_S insdcVersion_L insdcVersion_M insdcVersion_S insdcRawReadsAccession_L insdcRawReadsAccession_M insdcRawReadsAccession_S hash_L hash_M hash_S insdcAccessionBase_L insdcAccessionBase_M insdcAccessionBase_S insdcAccessionFull_L insdcAccessionFull_M insdcAccessionFull_S id hash fastaId +Chumakov Institute of Poliomyelitis and Viral Encephalitides Lukashev, A. N.; Klimentov, A. S.; Smirnova, S. E.; Dzagurova, T. K.; Drexler, J. F.; Gmyl, A. P. Uganda Homo sapiens 9606 2016-12-07T00:00:00Z GenBank Orthonairovirus haemorrhagiae 3052518 1958 Nakiwogo 2016-12-07T00:00:00Z 2016-12-07T00:00:00Z 1 1 7b10a4e21daa8a2e693958761be17d53 70954bc35782b5592858ac3f1a6bbf89 KX013483 KX013485 KX013483.1 KX013485.1 KX013483.1.L/KX013485.1.S bb3a6d8df47cb2891e7b60030a40c335 KX013483.1.L/KX013485.1.S_L, KX013483.1.L/KX013485.1.S_S diff --git a/integration-tests/tests/fixtures/sequence.fixture.ts b/integration-tests/tests/fixtures/sequence.fixture.ts index ea0b97659d..e2f55d4340 100644 --- a/integration-tests/tests/fixtures/sequence.fixture.ts +++ b/integration-tests/tests/fixtures/sequence.fixture.ts @@ -23,16 +23,13 @@ export const test = groupTest.extend({ collectionDate: '2021-10-15', authorAffiliations: 'Test Institute, France', }); - const fastaHeaderL = `${submissionId}_L`; - const fastaHeaderM = `${submissionId}_M`; - const fastaHeaderS = `${submissionId}_S`; await submissionPage.fillSequenceData({ - [fastaHeaderL]: + fastaHeaderL: 'CCACATTGACACAGANAGCTCCAGTAGTGGTTCTCTGTCCTTATTAAACCATGGACTTCTTAAGAAACCTTGACTGGACTCAGGTGATTGCTAGTCAGTATGTGACCAATCCCAGGTTTAATATCTCTGATTACTTCGAGATTGTTCGACAGCCTGGTGACGGGAACTGTTTCTACCACAGTATAGCTGAGTTAACCATGCCCAACAAAACAGATCACTCATACCATAACATCAAACATCTGACTGAGGTGGCAGCACGGAAGTATTATCAGGAGGAGCCGGAGGCTAAGCTCATTGGCCTGAGTCTGGAAGACTATCTTAAGAGGATGCTATCTGACAACGAATGGGGATCGACTCTTGAGGCATCTATGTTGGCTAAGGAAATGGGTATTACTATCATCATTTGGACTGTTGCAGCCAGTGACGAAGTGGAAGCAGGCATAAAGTTTGGTGATGGTGATGTGTTTACAGCCGTGAATCTTCTGCACTCCGGACAGACACACTTTGATGCCCTCAGAATACTGCCNCANTTTGAGGCTGACACAAGAGAGNCCTTNAGTCTGGTAGACAANNTNATAGCTGTGGACCANNTGACCTCNTCTTCAAGTGATGAANTGCAGGACTANGAAGANCTTGCTTTAGCACTTACNAGNGCGGAAGAACCATNTAGACGGTCTAGCNTGGATGAGGTNACCCTNTCTAAGAAACAAGCAGAGNTATTGAGGCAGAAGGCATCTCAGTTGTCNAAACTGGTTAATAAAAGTCAGAACATACCGACTAGAGTTGGCAGGGTTCTGGACTGTATGTTTAACTGCAAACTATGTGTTGAAATATCAGCTGACACTCTAATTCTGCGACCAGAATCTAAAGAAAGAATTGG', - [fastaHeaderM]: + fastaHeaderM: 'GTGGATTGAGCATCTTAATTGCAGCATACTTGTCAACATCATGCATATATCATTGATGTATGCAGTTTTCTGCTTGCAGCTGTGCGGTCTAGGGAAAACTAACGGACTACACAATGGGACTGAACACAATAAGACACACGTTATGACAACGCCTGATGACAGTCAGAGCCCTGAACCGCCAGTGAGCACAGCCCTGCCTGTCACACCGGACCCTTCCACTGTCACACCTACAACACCAGCCAGCGGATTAGAAGGCTCAGGAGAGGTTCACACATCCTCTCCAATCACCACCAAGGGTTTGTCTCTGCCGGGGGCTACATCTGAGCTCCCTGCGACTACTAGCATAGTCACTTCAGGTGCAAGTGATGCCGATTCTAGCACACAGGCAGCCAGAGACACCCCTAAACCATCAGTCCGCACGAGTCTGCCCAACAGCCCTAGCACACCATCCACACCACAAGGCACACACCATCCCGTGAGGAGTCTGCTTTCAGTCACGAGCCCTAAGCCAGAAGAAACACCAACACCGTCAAAATCAAGCAAAGATAGCTCAGCAACCAACAGTCCTCACCCAGCCGCCAGCAGACCAACAACCCCTCCCACAACAGCCCAGAGACCCGCTGAAAACAACAGCCACAACACCACCGAACAGCTTGAGTCCTTAACACAATTAGCAACTTCAGGTTCAATGATCTCTCCAACACAGACAGTCCTCCCAAAGAGTGTTACTTCTATAGCCATTCAAGACATTCATCCCAGCCCAACAAATAGGTCTAAAAGAAACCTTGATATGGAAATAATCT', - [fastaHeaderS]: + fastaHeaderS: 'GTGTTCTCTTGAGTGTTGGCAAAATGGAAAACAAAATCGAGGTGAACAACAAAGATGAGATGAACAAATGGTTTGAGGAGTTCAAGAAAGGAAATGGACTTGTGGACACTTTCACAAACTCNTATTCCTTTTGTGAAAGCGTNCCAAATCTGGACAGNTTTGTNTTCCAGATGGCNAGTGCCACTGATGATGCACAAAANGANTCCATCTACGCATCTGCNCTGGTGGANGCAACCAAATTTTGTGCACCTATATACGAGTGTGCTTGGGCTAGCTCCACTGGCATTGTTAAAAAGGGACTGGAGTGGTTCGAGAAAAATGCAGGAACCATTAAATCCTGGGATGAGAGTTATACTGAGCTTAAAGTTGAAGTTCCCAAAATAGAACAACTCTCCAACTACCAGCAGGCTGCTCTCAAATGGAGAAAAGACATAGGCTTCCGTGTCAATGCAAATACGGCAGCTTTGAGTAACAAAGTCCTAGCAGAGTACAAAGTTCCTGGCGAGATTGTAATGTCTGTCAAAGAGATGTTGTCAGATATGATTAGAAGNAGGAACCTGATTCTCAACAGAGGTGGTGATGAGAACCCACGCGGCCCAGTTAGCCGTGAACATGTGGAGTGGTGC', }); diff --git a/integration-tests/tests/readonly.setup.ts b/integration-tests/tests/readonly.setup.ts index dfef66b3bb..eb10a8520c 100644 --- a/integration-tests/tests/readonly.setup.ts +++ b/integration-tests/tests/readonly.setup.ts @@ -29,17 +29,16 @@ setup('Initialize some ebola sequences as base data', async ({ page }) => { } const submissionPage = new SingleSequenceSubmissionPage(page); - const submissionId = 'foobar-readonly'; const reviewPage = await submissionPage.completeSubmission( { - submissionId: submissionId, + submissionId: 'foobar-readonly', collectionCountry: 'France', collectionDate: '2021-05-12', authorAffiliations: 'Patho Institute, Paris', groupId: groupId.toString(), }, { - [submissionId]: + fastaHeader: 'nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn' + 'ATGGATAAACGGGTGAGAGGTTCATGGGCCCTGGGAGGACAATCTGAAGTTGATCTTGACTACCACAAAA' + 'TATTAACAGCCGGGCTTTCGGTCCAACAAGGGATTGTGCGACAAAGAGTCATCCCGGTATATGTTGTGAG' + diff --git a/integration-tests/tests/specs/cli/end-to-end-flow.spec.ts b/integration-tests/tests/specs/cli/end-to-end-flow.spec.ts index f907ebaf83..1913789aa7 100644 --- a/integration-tests/tests/specs/cli/end-to-end-flow.spec.ts +++ b/integration-tests/tests/specs/cli/end-to-end-flow.spec.ts @@ -17,9 +17,9 @@ cliTest.describe('CLI End-to-End Submission Flow', () => { const submissionId1 = `cli_e2e_${timestamp}_001`; const submissionId2 = `cli_e2e_${timestamp}_002`; - const testMetadata = `authorAffiliations\tauthors\tgeoLocCountry\thostNameScientific\thostTaxonId\tsampleCollectionDate\tspecimenCollectorSampleId\tsubmissionId -"National Institute of Health, Department of Virology"\t"Ammar, M.; Salman, M.; Umair, M.; Ali, Q.; Hakim, R.; Haider, S.A.; Jamal, Z."\tPakistan\tHomo sapiens\t9606\t2023-08-26\tCCHF/NIHPAK-19/2023\t${submissionId1} -"Research Lab, University of Example"\t"Example, A.; Test, B.; Sample, C."\tColombia\tHomo sapiens\t9606\t2021-12-12\tXF499\t${submissionId2}`; + const testMetadata = `authorAffiliations\tauthors\tgeoLocCountry\thostNameScientific\thostTaxonId\tsampleCollectionDate\tspecimenCollectorSampleId\tsubmissionId\tfastaId +"National Institute of Health, Department of Virology"\t"Ammar, M.; Salman, M.; Umair, M.; Ali, Q.; Hakim, R.; Haider, S.A.; Jamal, Z."\tPakistan\tHomo sapiens\t9606\t2023-08-26\tCCHF/NIHPAK-19/2023\t${submissionId1}\t${submissionId1}_L, ${submissionId1}_M, ${submissionId1}_S +"Research Lab, University of Example"\t"Example, A.; Test, B.; Sample, C."\tColombia\tHomo sapiens\t9606\t2021-12-12\tXF499\t${submissionId2}\t${submissionId2}_L, ${submissionId2}_M, ${submissionId2}_S`; const testSequences = `>${submissionId1}_L CCACATTGACACAGANAGCTCCAGTAGTGGTTCTCTGTCCTTATTAAACCATGGACTTCTTAAGAAACCTTGACTGGACTCAGGTGATTGCTAGTCAGTATGTGACCAATCCCAGGTTTAATATCTCTGATTACTTCGAGATTGTTCGACAGCCTGGTGACGGGAACTGTTTCTACCACAGTATAGCTGAGTTAACCATGCCCAACAAAACAGATCACTCATACCATAACATCAAACATCTGACTGAGGTGGCAGCACGGAAGTATTATCAGGAGGAGCCGGAGGCTAAGCTCATTGGCCTGAGTCTGGAAGACTATCTTAAGAGGATGCTATCTGACAACGAATGGGGATCGACTCTTGAGGCATCTATGTTGGCTAAGGAAATGGGTATTACTATCATCATTTGGACTGTTGCAGCCAGTGACGAAGTGGAAGCAGGCATAAAGTTTGGTGATGGTGATGTGTTTACAGCCGTGAATCTTCTGCACTCCGGACAGACACACTTTGATGCCCTCAGAATACTGCCNCANTTTGAGGCTGACACAAGAGAGNCCTTNAGTCTGGTAGACAANNTNATAGCTGTGGACCANNTGACCTCNTCTTCAAGTGATGAANTGCAGGACTANGAAGANCTTGCTTTAGCACTTACNAGNGCGGAAGAACCATNTAGACGGTCTAGCNTGGATGAGGTNACCCTNTCTAAGAAACAAGCAGAGNTATTGAGGCAGAAGGCATCTCAGTTGTCNAAACTGGTTAATAAAAGTCAGAACATACCGACTAGAGTTGGCAGGGTTCTGGACTGTATGTTTAACTGCAAACTATGTGTTGAAATATCAGCTGACACTCTAATTCTGCGACCAGAATCTAAAGAAAGAATTGG diff --git a/integration-tests/tests/specs/features/revise-sequence.spec.ts b/integration-tests/tests/specs/features/revise-sequence.spec.ts index e339028018..bd1cf1fce0 100644 --- a/integration-tests/tests/specs/features/revise-sequence.spec.ts +++ b/integration-tests/tests/specs/features/revise-sequence.spec.ts @@ -39,13 +39,15 @@ sequenceTest( await page.getByRole('link', { name: 'Revise this sequence' }).click({ timeout: 15000 }); await expect(page.getByRole('heading', { name: 'Create new revision from' })).toBeVisible(); - await page.getByTestId('discard_L_segment_file').click(); - await page.getByTestId('discard_S_segment_file').click(); - // await page.getByTestId('Add a segment_segment_file').setInputFiles({ - // name: 'update_S.txt', - // mimeType: 'text/plain', - // buffer: Buffer.from('>S\nAAAAA'), - // }); + await page.getByTestId(/^discard.*L\)_segment_file$/).click(); + await page.getByTestId(/^discard.*S\)_segment_file$/).click(); + const newSsequence = + 'CAAATGGTTTGAGGAGTTCAAGAAAGGAAATGGACTTGTGGACACTTTCACAAACTCNTATTCCTTTTGTGAAAGCGTNCCAAATCTGGACAGNTTTGTNTTCCAGATGGCNAGTGCCACTGATGATGCACAAAANGANTCCATCTACGCATCTGCNCTGGTGGANGCAACCAAATTTTGTGCACCTATATACGAGTGTGCTTGGGCTAGCTCCACTGGCATTGTTAAAAAGGGACTGGAGTGGTTCGAGAAAAATGCAGGAACCATTAAATCCTGGGATGAGAGTTATACTGAGCTTAAAGTTGAAGTTCCCAAAATAGAACAACTCTCCAACTACCAGCAGGCTGCTCTCAAATGGAGAAAAGACATAGGCTTCCGTGTCAATGCAAATACGGCAGCTTTGAGTAACAAAGTCCTAGCAGAGTACAAAGTTCCTGGCGAGATTGTAATGTCTGTCAAAGAGATGTTGTCAGATATGATTAGAAGNAGGAACCTGATTCTCAACAGAGGTGGTGATGAGAACCCACGCGGCCCAGTTAGCCGTGAACATGTGGAGTGGTGCAGGGAATTCGTCAAAGGCAAGTACATAATGGCTTTCAACCCACCCTGGGGAGACATCAACAAGTCAGGCCGTTCAGGAATAGCACTTGTTGCAACAGGCCTTGCCAAGCTTGCAGAGACTGAAGGGAAGGGAGTTTTTGACGAAGCCAAGAAGACTATAGAGGCTCTTAACGGGTATCTGGACAAGCATAAGGATGAAGTTGACAAAGCAAGTGCCGACAGCATGATAACAAACCTCCTTAAGCACATTGCTAAGGCACAAGAGCTTTACAAAAACTCGTCTGCTCTTCGTGCTCAGGGTGCACAGATTGACACCGTCTTCAGCTCATACTACTGGCTCTACAAGGCCGGTGTGACTCCAGAGACCTTCCCGACTGTTTCACAGTTCCTTTTTGAGTTAGGGAAGCAACCAAGGGGCACCAAGAAAATGAAGAAGGCACTCCTGAGCACCCCAATGAAGTGGGGAAAGAAGCTTTATGAGCTTTTTGCTGATGATTCCTTCCAACAGAACAGGATCTACATGCACCCCGCTGTGCTAACAGCTGGCAGAATCAGTGAAATGGGTGTCTGCTTCGGAACAATCCCTGTGGCCAATCCTGATGATGCCGCCTTAGGATCTGGACACACCAAGTCCATTCTCAACCTTCGGACAAACACTGAGACCAACAATCCGTGTGCCAAGACAATTGTTAAGTTGTTTGAAATTCANAAAACAGGGTTNAACATACAGGACATGGANATTGTGGCCTCNGAGCATCTGCTGCACCAATCCCTTGTTGGCAAGCAGTCTCCATTTCAAAATGCTTACAACGTCAAGGGGAANGCCACCAGTGCCAANATCATCTAAAGCNNANAATNNTCTNCAATCAGCTTTNCC'; + await page.getByTestId('Add a segment_segment_file').setInputFiles({ + name: 'update_S.txt', + mimeType: 'text/plain', + buffer: Buffer.from('>S\n' + newSsequence), + }); await page.getByRole('button', { name: 'Submit' }).click(); await page.getByRole('button', { name: 'Confirm' }).click(); @@ -58,9 +60,9 @@ sequenceTest( expect(tabs).not.toContain('L (aligned)'); expect(tabs).not.toContain('L (unaligned)'); - // expect(tabs).toContain('S (unaligned)'); - // await reviewPage.switchSequenceTab('S (unaligned)'); - // expect(await reviewPage.getSequenceContent()).toBe('AAAAA'); + expect(tabs).toContain('S (unaligned)'); + await reviewPage.switchSequenceTab('S (unaligned)'); + expect(await reviewPage.getSequenceContent()).toBe(newSsequence); await reviewPage.closeSequencesDialog(); }, diff --git a/integration-tests/tests/specs/features/search/override-hidden-fields.spec.ts b/integration-tests/tests/specs/features/search/override-hidden-fields.spec.ts index 28e9947752..f2515d8ee9 100644 --- a/integration-tests/tests/specs/features/search/override-hidden-fields.spec.ts +++ b/integration-tests/tests/specs/features/search/override-hidden-fields.spec.ts @@ -21,7 +21,8 @@ test('Override hidden fields', async ({ page, groupId }) => { authorAffiliations: uuid, }, { - foo1: 'ATTGATCTCATCATTTACCAATTGGAGACCGTTTAACTAGTCAATCCCCCATTTGGGGGCATTCCTAAAGTGTTGCAAAGGTATGTGGGTCGTATTGCTTTGCCTTTTCCTAACCTGGCTCCTCCTACAATTCTAACCTGCTTGATAAGTGTGATTACCTGAGTAATAGACTAATTTCGTCCTGGTAATTAGCATTTTCTAGTAAAACCAATACTATCTCAAGTCCTAAGAGGAGGTGAGAAGAGGGTCTCGAGGTATCCCTCCAGTCCACAAAATCTAGCTAATTTTAGCTGAGTGGACTGATTACTCTCATCACACGCTAACTACTAAGGGTTTACCTGAGAGCCTACAACATGGATAAACGGGTGAGAGGTTCATGGGCCCTGGGAGGACAATCTGAAGTTGATCTTGACTACCACAAAATATTAACAGCCGGGCTTTCGGTCCAACAAGGGATTGTGCGACAAAGAGTCATCCCGGTATATGTTGTGAGTGATCTTGAGGGTATTTGTCAACATATCATTCAGGCCTTTGAAGCAGGCGTAGATTTCCAAGATAATGCTGACAGCTTCCTTTTACTTTTATGTTTACATCATGCTTACCAAGGAGATCATAGGCTCTTCCTCAAAAGTGATGCAGTTCAATACTTAGAGGGCCATGGTTTCAGGTTTGAGGTCCGAGAAAAGGAGAATGTGCACCGTCTGGATGAATTGTTGCCCAATGTCACCGGTGGAAAAAATCTTAGGAGAACATTGGCTGCAATGCCTGAAGAGGAGACAACAGAAGCTAACGCTGGTCAGTTTTTATCCTTTGCCAGTTTGTTTCTACCCAAACTTGTCGTTGGGGAGAAAGCGTGTCTGGAAAAAGTACAAAGGCAGATTCAGGTCCATGCAGAACAAGGGCTCATTCAATATCCAACTTCCTGGCAATCAGTTGGACACATGATGGTGATCTTCCGTTTGATGAGAACAAACTTTTTAATCAAGTTCCTACTAATACATCAGGGGATGCACATGG', + fastaHeader: + 'ATTGATCTCATCATTTACCAATTGGAGACCGTTTAACTAGTCAATCCCCCATTTGGGGGCATTCCTAAAGTGTTGCAAAGGTATGTGGGTCGTATTGCTTTGCCTTTTCCTAACCTGGCTCCTCCTACAATTCTAACCTGCTTGATAAGTGTGATTACCTGAGTAATAGACTAATTTCGTCCTGGTAATTAGCATTTTCTAGTAAAACCAATACTATCTCAAGTCCTAAGAGGAGGTGAGAAGAGGGTCTCGAGGTATCCCTCCAGTCCACAAAATCTAGCTAATTTTAGCTGAGTGGACTGATTACTCTCATCACACGCTAACTACTAAGGGTTTACCTGAGAGCCTACAACATGGATAAACGGGTGAGAGGTTCATGGGCCCTGGGAGGACAATCTGAAGTTGATCTTGACTACCACAAAATATTAACAGCCGGGCTTTCGGTCCAACAAGGGATTGTGCGACAAAGAGTCATCCCGGTATATGTTGTGAGTGATCTTGAGGGTATTTGTCAACATATCATTCAGGCCTTTGAAGCAGGCGTAGATTTCCAAGATAATGCTGACAGCTTCCTTTTACTTTTATGTTTACATCATGCTTACCAAGGAGATCATAGGCTCTTCCTCAAAAGTGATGCAGTTCAATACTTAGAGGGCCATGGTTTCAGGTTTGAGGTCCGAGAAAAGGAGAATGTGCACCGTCTGGATGAATTGTTGCCCAATGTCACCGGTGGAAAAAATCTTAGGAGAACATTGGCTGCAATGCCTGAAGAGGAGACAACAGAAGCTAACGCTGGTCAGTTTTTATCCTTTGCCAGTTTGTTTCTACCCAAACTTGTCGTTGGGGAGAAAGCGTGTCTGGAAAAAGTACAAAGGCAGATTCAGGTCCATGCAGAACAAGGGCTCATTCAATATCCAACTTCCTGGCAATCAGTTGGACACATGATGGTGATCTTCCGTTTGATGAGAACAAACTTTTTAATCAAGTTCCTACTAATACATCAGGGGATGCACATGG', }, ); await page.goto('/'); @@ -33,7 +34,8 @@ test('Override hidden fields', async ({ page, groupId }) => { authorAffiliations: uuid, }, { - foo1: 'ATTGATCTCATCATTTACCAATTGGAGACCGTTTAACTAGTCAATCCCCCATTTGGGGGCATTCCTAAAGTGTTGCAAAGGTATGTGGGTCGTATTGCTTTGCCTTTTCCTAACCTGGCTCCTCCTACAATTCTAACCTGCTTGATAAGTGTGATTACCTGAGTAATAGACTAATTTCGTCCTGGTAATTAGCATTTTCTAGTAAAACCAATACTATCTCAAGTCCTAAGAGGAGGTGAGAAGAGGGTCTCGAGGTATCCCTCCAGTCCACAAAATCTAGCTAATTTTAGCTGAGTGGACTGATTACTCTCATCACACGCTAACTACTAAGGGTTTACCTGAGAGCCTACAACATGGATAAACGGGTGAGAGGTTCATGGGCCCTGGGAGGACAATCTGAAGTTGATCTTGACTACCACAAAATATTAACAGCCGGGCTTTCGGTCCAACAAGGGATTGTGCGACAAAGAGTCATCCCGGTATATGTTGTGAGTGATCTTGAGGGTATTTGTCAACATATCATTCAGGCCTTTGAAGCAGGCGTAGATTTCCAAGATAATGCTGACAGCTTCCTTTTACTTTTATGTTTACATCATGCTTACCAAGGAGATCATAGGCTCTTCCTCAAAAGTGATGCAGTTCAATACTTAGAGGGCCATGGTTTCAGGTTTGAGGTCCGAGAAAAGGAGAATGTGCACCGTCTGGATGAATTGTTGCCCAATGTCACCGGTGGAAAAAATCTTAGGAGAACATTGGCTGCAATGCCTGAAGAGGAGACAACAGAAGCTAACGCTGGTCAGTTTTTATCCTTTGCCAGTTTGTTTCTACCCAAACTTGTCGTTGGGGAGAAAGCGTGTCTGGAAAAAGTACAAAGGCAGATTCAGGTCCATGCAGAACAAGGGCTCATTCAATATCCAACTTCCTGGCAATCAGTTGGACACATGATGGTGATCTTCCGTTTGATGAGAACAAACTTTTTAATCAAGTTCCTACTAATACATCAGGGGATGCACATGG', + fastaHeader: + 'ATTGATCTCATCATTTACCAATTGGAGACCGTTTAACTAGTCAATCCCCCATTTGGGGGCATTCCTAAAGTGTTGCAAAGGTATGTGGGTCGTATTGCTTTGCCTTTTCCTAACCTGGCTCCTCCTACAATTCTAACCTGCTTGATAAGTGTGATTACCTGAGTAATAGACTAATTTCGTCCTGGTAATTAGCATTTTCTAGTAAAACCAATACTATCTCAAGTCCTAAGAGGAGGTGAGAAGAGGGTCTCGAGGTATCCCTCCAGTCCACAAAATCTAGCTAATTTTAGCTGAGTGGACTGATTACTCTCATCACACGCTAACTACTAAGGGTTTACCTGAGAGCCTACAACATGGATAAACGGGTGAGAGGTTCATGGGCCCTGGGAGGACAATCTGAAGTTGATCTTGACTACCACAAAATATTAACAGCCGGGCTTTCGGTCCAACAAGGGATTGTGCGACAAAGAGTCATCCCGGTATATGTTGTGAGTGATCTTGAGGGTATTTGTCAACATATCATTCAGGCCTTTGAAGCAGGCGTAGATTTCCAAGATAATGCTGACAGCTTCCTTTTACTTTTATGTTTACATCATGCTTACCAAGGAGATCATAGGCTCTTCCTCAAAAGTGATGCAGTTCAATACTTAGAGGGCCATGGTTTCAGGTTTGAGGTCCGAGAAAAGGAGAATGTGCACCGTCTGGATGAATTGTTGCCCAATGTCACCGGTGGAAAAAATCTTAGGAGAACATTGGCTGCAATGCCTGAAGAGGAGACAACAGAAGCTAACGCTGGTCAGTTTTTATCCTTTGCCAGTTTGTTTCTACCCAAACTTGTCGTTGGGGAGAAAGCGTGTCTGGAAAAAGTACAAAGGCAGATTCAGGTCCATGCAGAACAAGGGCTCATTCAATATCCAACTTCCTGGCAATCAGTTGGACACATGATGGTGATCTTCCGTTTGATGAGAACAAACTTTTTAATCAAGTTCCTACTAATACATCAGGGGATGCACATGG', }, ); diff --git a/integration-tests/tests/specs/features/sequence-view.dependent.spec.ts b/integration-tests/tests/specs/features/sequence-view.dependent.spec.ts index c05cc17a8c..ce447cbf3a 100644 --- a/integration-tests/tests/specs/features/sequence-view.dependent.spec.ts +++ b/integration-tests/tests/specs/features/sequence-view.dependent.spec.ts @@ -11,25 +11,21 @@ test.describe('Sequence view in review card', () => { test.setTimeout(120000); void groupId; const submissionPage = new SingleSequenceSubmissionPage(page); - const submissionId = 'TEST_SEQ_VIEW'; await submissionPage.navigateToSubmissionPage('Crimean-Congo Hemorrhagic Fever Virus'); await submissionPage.fillSubmissionForm({ - submissionId: submissionId, + submissionId: 'TEST_SEQ_VIEW', collectionCountry: 'Brazil', collectionDate: '2023-01-15', authorAffiliations: 'Test Lab, University of Testing', }); - const fastaHeaderL = `${submissionId}_L`; - const fastaHeaderM = `${submissionId}_M`; - const fastaHeaderS = `${submissionId}_S`; await submissionPage.fillSequenceData({ - [fastaHeaderL]: + fastaHeaderL: 'CCACATTGACACAGANAGCTCCAGTAGTGGTTCTCTGTCCTTATTAAACCATGGACTTCTTAAGAAACCTTGACTGGACTCAGGTGATTGCTAGTCAGTATGTGACCAATCCC', - [fastaHeaderM]: + fastaHeaderM: 'GTGGATTGAGCATCTTAATTGCAGCATACTTGTCAACATCATGCATATATCATTGATGTATGCAGTTTTCTGCTTGCAGCTGTGCGGTCTAGGGAAAACTAACGGACTACACA', - [fastaHeaderS]: + fastaHeaderS: 'GTGTTCTCTTGAGTGTTGGCAAAATGGAAAACAAAATCGAGGTGAACAACAAAGATGAGATGAACAAATGGTTTGAGGAGTTCAAGAAAGGAAATGGACTTGTGGACACTTTC', }); await submissionPage.acceptTerms(); diff --git a/integration-tests/tests/specs/features/single-submit.spec.ts b/integration-tests/tests/specs/features/single-submit.spec.ts index 99b1ee2b25..b7a9390be0 100644 --- a/integration-tests/tests/specs/features/single-submit.spec.ts +++ b/integration-tests/tests/specs/features/single-submit.spec.ts @@ -5,17 +5,16 @@ test('submit a single sequence', async ({ page, groupId }) => { test.setTimeout(90_000); void groupId; const submissionPage = new SingleSequenceSubmissionPage(page); - const submissionId = 'TEST-ID-123'; await submissionPage.navigateToSubmissionPage(); await submissionPage.fillSubmissionForm({ - submissionId: submissionId, + submissionId: 'TEST-ID-123', collectionCountry: 'Uganda', collectionDate: '2023-10-15', authorAffiliations: 'Research Lab, University', }); await submissionPage.fillSequenceData({ - [submissionId]: + fastaHeader: 'ATTGATCTCATCATTTACCAATTGGAGACCGTTTAACTAGTCAATCCCCCATTTGGGGGCATTCCTAAAGTGTTGCAAAGGTATGTGGGTCGTATTGCTTTGCCTTTTCCTAACCTGGCTCCTCCTACAATTCTAACCTGCTTGATAAGTGTGATTACCTGAGTAATAGACTAATTTCGTCCTGGTAATTAGCATTTTCTAGTAAAACCAATACTATCTCAAGTCCTAAGAGGAGGTGAGAAGAGGGTCTCGAGGTATCCCTCCAGTCCACAAAATCTAGCTAATTTTAGCTGAGTGGACTGATTACTCTCATCACACGCTAACTACTAAGGGTTTACCTGAGAGCCTACAACATGGATAAACGGGTGAGAGGTTCATGGGCCCTGGGAGGACAATCTGAAGTTGATCTTGACTACCACAAAATATTAACAGCCGGGCTTTCGGTCCAACAAGGGATTGTGCGACAAAGAGTCATCCCGGTATATGTTGTGAGTGATCTTGAGGGTATTTGTCAACATATCATTCAGGCCTTTGAAGCAGGCGTAGATTTCCAAGATAATGCTGACAGCTTCCTTTTACTTTTATGTTTACATCATGCTTACCAAGGAGATCATAGGCTCTTCCTCAAAAGTGATGCAGTTCAATACTTAGAGGGCCATGGTTTCAGGTTTGAGGTCCGAGAAAAGGAGAATGTGCACCGTCTGGATGAATTGTTGCCCAATGTCACCGGTGGAAAAAATCTTAGGAGAACATTGGCTGCAATGCCTGAAGAGGAGACAACAGAAGCTAACGCTGGTCAGTTTTTATCCTTTGCCAGTTTGTTTCTACCCAAACTTGTCGTTGGGGAGAAAGCGTGTCTGGAAAAAGTACAAAGGCAGATTCAGGTCCATGCAGAACAAGGGCTCATTCAATATCCAACTTCCTGGCAATCAGTTGGACACATGATGGTGATCTTCCGTTTGATGAGAACAAACTTTTTAATCAAGTTCCTACTAATACATCAGGGGATGCACATGG', }); await submissionPage.acceptTerms(); @@ -27,16 +26,15 @@ test('submit a single sequence, all in one method', async ({ page, groupId }) => test.setTimeout(90_000); void groupId; const submissionPage = new SingleSequenceSubmissionPage(page); - const submissionId = 'TEST-ID-123'; await submissionPage.completeSubmission( { - submissionId: submissionId, + submissionId: 'TEST-ID-123', collectionCountry: 'Uganda', collectionDate: '2023-10-15', authorAffiliations: 'Research Lab, University', }, { - [submissionId]: + fastaHeader: 'ATTGATCTCATCATTTACCAATTGGAGACCGTTTAACTAGTCAATCCCCCATTTGGGGGCATTCCTAAAGTGTTGCAAAGGTATGTGGGTCGTATTGCTTTGCCTTTTCCTAACCTGGCTCCTCCTACAATTCTAACCTGCTTGATAAGTGTGATTACCTGAGTAATAGACTAATTTCGTCCTGGTAATTAGCATTTTCTAGTAAAACCAATACTATCTCAAGTCCTAAGAGGAGGTGAGAAGAGGGTCTCGAGGTATCCCTCCAGTCCACAAAATCTAGCTAATTTTAGCTGAGTGGACTGATTACTCTCATCACACGCTAACTACTAAGGGTTTACCTGAGAGCCTACAACATGGATAAACGGGTGAGAGGTTCATGGGCCCTGGGAGGACAATCTGAAGTTGATCTTGACTACCACAAAATATTAACAGCCGGGCTTTCGGTCCAACAAGGGATTGTGCGACAAAGAGTCATCCCGGTATATGTTGTGAGTGATCTTGAGGGTATTTGTCAACATATCATTCAGGCCTTTGAAGCAGGCGTAGATTTCCAAGATAATGCTGACAGCTTCCTTTTACTTTTATGTTTACATCATGCTTACCAAGGAGATCATAGGCTCTTCCTCAAAAGTGATGCAGTTCAATACTTAGAGGGCCATGGTTTCAGGTTTGAGGTCCGAGAAAAGGAGAATGTGCACCGTCTGGATGAATTGTTGCCCAATGTCACCGGTGGAAAAAATCTTAGGAGAACATTGGCTGCAATGCCTGAAGAGGAGACAACAGAAGCTAACGCTGGTCAGTTTTTATCCTTTGCCAGTTTGTTTCTACCCAAACTTGTCGTTGGGGAGAAAGCGTGTCTGGAAAAAGTACAAAGGCAGATTCAGGTCCATGCAGAACAAGGGCTCATTCAATATCCAACTTCCTGGCAATCAGTTGGACACATGATGGTGATCTTCCGTTTGATGAGAACAAACTTTTTAATCAAGTTCCTACTAATACATCAGGGGATGCACATGG', }, ); diff --git a/integration-tests/tests/specs/features/submission-flow.spec.ts b/integration-tests/tests/specs/features/submission-flow.spec.ts index 1825faa22b..14e7e1e7be 100644 --- a/integration-tests/tests/specs/features/submission-flow.spec.ts +++ b/integration-tests/tests/specs/features/submission-flow.spec.ts @@ -67,24 +67,20 @@ test.describe('Submission flow', () => { test.setTimeout(120_000); void groupId; const submissionPage = new SingleSequenceSubmissionPage(page); - const submissionId = 'XF499'; await submissionPage.navigateToSubmissionPage('Crimean-Congo Hemorrhagic Fever Virus'); await submissionPage.fillSubmissionForm({ - submissionId: submissionId, + submissionId: 'XF499', collectionCountry: 'Colombia', collectionDate: '2021-12-12', authorAffiliations: 'Research Lab, University of Example', }); - const fastaHeaderL = `${submissionId}_L`; - const fastaHeaderM = `${submissionId}_M`; - const fastaHeaderS = `${submissionId}_S`; await submissionPage.fillSequenceData({ - [fastaHeaderL]: + fastaHeaderL: 'CCACATTGACACAGANAGCTCCAGTAGTGGTTCTCTGTCCTTATTAAACCATGGACTTCTTAAGAAACCTTGACTGGACTCAGGTGATTGCTAGTCAGTATGTGACCAATCCCAGGTTTAATATCTCTGATTACTTCGAGATTGTTCGACAGCCTGGTGACGGGAACTGTTTCTACCACAGTATAGCTGAGTTAACCATGCCCAACAAAACAGATCACTCATACCATAACATCAAACATCTGACTGAGGTGGCAGCACGGAAGTATTATCAGGAGGAGCCGGAGGCTAAGCTCATTGGCCTGAGTCTGGAAGACTATCTTAAGAGGATGCTATCTGACAACGAATGGGGATCGACTCTTGAGGCATCTATGTTGGCTAAGGAAATGGGTATTACTATCATCATTTGGACTGTTGCAGCCAGTGACGAAGTGGAAGCAGGCATAAAGTTTGGTGATGGTGATGTGTTTACAGCCGTGAATCTTCTGCACTCCGGACAGACACACTTTGATGCCCTCAGAATACTGCCNCANTTTGAGGCTGACACAAGAGAGNCCTTNAGTCTGGTAGACAANNTNATAGCTGTGGACCANNTGACCTCNTCTTCAAGTGATGAANTGCAGGACTANGAAGANCTTGCTTTAGCACTTACNAGNGCGGAAGAACCATNTAGACGGTCTAGCNTGGATGAGGTNACCCTNTCTAAGAAACAAGCAGAGNTATTGAGGCAGAAGGCATCTCAGTTGTCNAAACTGGTTAATAAAAGTCAGAACATACCGACTAGAGTTGGCAGGGTTCTGGACTGTATGTTTAACTGCAAACTATGTGTTGAAATATCAGCTGACACTCTAATTCTGCGACCAGAATCTAAAGAAAGAATTGG', - [fastaHeaderM]: + fastaHeaderM: 'GTGGATTGAGCATCTTAATTGCAGCATACTTGTCAACATCATGCATATATCATTGATGTATGCAGTTTTCTGCTTGCAGCTGTGCGGTCTAGGGAAAACTAACGGACTACACAATGGGACTGAACACAATAAGACACACGTTATGACAACGCCTGATGACAGTCAGAGCCCTGAACCGCCAGTGAGCACAGCCCTGCCTGTCACACCGGACCCTTCCACTGTCACACCTACAACACCAGCCAGCGGATTAGAAGGCTCAGGAGAGGTTCACACATCCTCTCCAATCACCACCAAGGGTTTGTCTCTGCCGGGGGCTACATCTGAGCTCCCTGCGACTACTAGCATAGTCACTTCAGGTGCAAGTGATGCCGATTCTAGCACACAGGCAGCCAGAGACACCCCTAAACCATCAGTCCGCACGAGTCTGCCCAACAGCCCTAGCACACCATCCACACCACAAGGCACACACCATCCCGTGAGGAGTCTGCTTTCAGTCACGAGCCCTAAGCCAGAAGAAACACCAACACCGTCAAAATCAAGCAAAGATAGCTCAGCAACCAACAGTCCTCACCCAGCCGCCAGCAGACCAACAACCCCTCCCACAACAGCCCAGAGACCCGCTGAAAACAACAGCCACAACACCACCGAACAGCTTGAGTCCTTAACACAATTAGCAACTTCAGGTTCAATGATCTCTCCAACACAGACAGTCCTCCCAAAGAGTGTTACTTCTATAGCCATTCAAGACATTCATCCCAGCCCAACAAATAGGTCTAAAAGAAACCTTGATATGGAAATAATCT', - [fastaHeaderS]: + fastaHeaderS: 'GTGTTCTCTTGAGTGTTGGCAAAATGGAAAACAAAATCGAGGTGAACAACAAAGATGAGATGAACAAATGGTTTGAGGAGTTCAAGAAAGGAAATGGACTTGTGGACACTTTCACAAACTCNTATTCCTTTTGTGAAAGCGTNCCAAATCTGGACAGNTTTGTNTTCCAGATGGCNAGTGCCACTGATGATGCACAAAANGANTCCATCTACGCATCTGCNCTGGTGGANGCAACCAAATTTTGTGCACCTATATACGAGTGTGCTTGGGCTAGCTCCACTGGCATTGTTAAAAAGGGACTGGAGTGGTTCGAGAAAAATGCAGGAACCATTAAATCCTGGGATGAGAGTTATACTGAGCTTAAAGTTGAAGTTCCCAAAATAGAACAACTCTCCAACTACCAGCAGGCTGCTCTCAAATGGAGAAAAGACATAGGCTTCCGTGTCAATGCAAATACGGCAGCTTTGAGTAACAAAGTCCTAGCAGAGTACAAAGTTCCTGGCGAGATTGTAATGTCTGTCAAAGAGATGTTGTCAGATATGATTAGAAGNAGGAACCTGATTCTCAACAGAGGTGGTGATGAGAACCCACGCGGCCCAGTTAGCCGTGAACATGTGGAGTGGTGC', }); await submissionPage.acceptTerms(); diff --git a/integration-tests/tests/specs/features/trim-ns.spec.ts b/integration-tests/tests/specs/features/trim-ns.spec.ts index d9bcd1ad3b..49fb6b3731 100644 --- a/integration-tests/tests/specs/features/trim-ns.spec.ts +++ b/integration-tests/tests/specs/features/trim-ns.spec.ts @@ -22,9 +22,8 @@ test.describe('Sequence N trimming functionality', () => { const lSegmentWithNs = 'NNNNNNNNNNTTCAACAAGCAAAGCCAACTGTGACGGTGTTCTATATGCTAAAAGGTAACTTGATGAACACAGAGCCAACAGTTGCTGAGCTTGTCAGCTATGGTATAAAGGAAGGCAGGTTTTATAGGCTTTCCGACACCGGAATCAATGCAACCACATANNNNNN'; - const fastaHeaderL = 'TRIM_NS_TEST_L'; await submissionPage.fillSequenceData({ - [fastaHeaderL]: lSegmentWithNs, + fastaHeaderL: lSegmentWithNs, }); await submissionPage.acceptTerms(); diff --git a/integration-tests/tests/test-data/cchfv_test_metadata.tsv b/integration-tests/tests/test-data/cchfv_test_metadata.tsv index 3840e2630f..27b2ad474e 100644 --- a/integration-tests/tests/test-data/cchfv_test_metadata.tsv +++ b/integration-tests/tests/test-data/cchfv_test_metadata.tsv @@ -1,2 +1,2 @@ -ampliconPcrPrimerScheme ampliconSize anatomicalMaterial anatomicalPart authorAffiliations authors bodyProduct breadthOfCoverage cellLine collectionDevice collectionMethod consensusSequenceSoftwareName consensusSequenceSoftwareVersion cultureId dehostingMethod depthOfCoverage diagnostic_measurement_method diagnosticMeasurementUnit diagnosticMeasurementValue diagnosticTargetGeneName diagnosticTargetPresence environmentalMaterial environmentalSite experimentalSpecimenRoleType exposureDetails exposureEvent exposureSetting foodProduct foodProductProperties geoLocAdmin1 geoLocAdmin2 geoLocCity geoLocCountry geoLocSite hostAge hostAgeBin hostDisease hostGender hostHealthOutcome hostHealthState hostNameCommon hostNameScientific hostOriginCountry hostRole hostTaxonId hostVaccinationStatus passageMethod passageNumber presamplingActivity previousInfectionDisease previousInfectionOrganism purposeOfSampling purposeOfSequencing qualityControlDetails qualityControlDetermination qualityControlIssues qualityControlMethodName qualityControlMethodVersion rawSequenceDataProcessingMethod referenceGenomeAccession sampleCollectionDate sampleReceivedDate sampleType sequencedByContactEmail sequencedByContactName sequencedByOrganization sequencingAssayType sequencingDate sequencingInstrument sequencingProtocol signsAndSymptoms specimenCollectorSampleId specimenProcessing specimenProcessingDetails bioprojectAccessions submissionId travelHistory versionComment - "National Institute of Health, Department of Virology" "Ammar, M.; Salman, M.; Umair, M.; Ali, Q.; Hakim, R.; Haider, S.A.; Jamal, Z." Hogwarts Pakistan Homo sapiens 9606 2023-08-26 CCHF/NIHPAK-19/2023 test_NIHPAK-19 "OR964915.1,OR964926.1,OR964937.1" +fastaId ampliconPcrPrimerScheme ampliconSize anatomicalMaterial anatomicalPart authorAffiliations authors bodyProduct breadthOfCoverage cellLine collectionDevice collectionMethod consensusSequenceSoftwareName consensusSequenceSoftwareVersion cultureId dehostingMethod depthOfCoverage diagnostic_measurement_method diagnosticMeasurementUnit diagnosticMeasurementValue diagnosticTargetGeneName diagnosticTargetPresence environmentalMaterial environmentalSite experimentalSpecimenRoleType exposureDetails exposureEvent exposureSetting foodProduct foodProductProperties geoLocAdmin1 geoLocAdmin2 geoLocCity geoLocCountry geoLocSite hostAge hostAgeBin hostDisease hostGender hostHealthOutcome hostHealthState hostNameCommon hostNameScientific hostOriginCountry hostRole hostTaxonId hostVaccinationStatus passageMethod passageNumber presamplingActivity previousInfectionDisease previousInfectionOrganism purposeOfSampling purposeOfSequencing qualityControlDetails qualityControlDetermination qualityControlIssues qualityControlMethodName qualityControlMethodVersion rawSequenceDataProcessingMethod referenceGenomeAccession sampleCollectionDate sampleReceivedDate sampleType sequencedByContactEmail sequencedByContactName sequencedByOrganization sequencingAssayType sequencingDate sequencingInstrument sequencingProtocol signsAndSymptoms specimenCollectorSampleId specimenProcessing specimenProcessingDetails bioprojectAccessions submissionId travelHistory versionComment +test_NIHPAK-19_L, test_NIHPAK-19_M, test_NIHPAK-19_S "National Institute of Health, Department of Virology" "Ammar, M.; Salman, M.; Umair, M.; Ali, Q.; Hakim, R.; Haider, S.A.; Jamal, Z." Hogwarts Pakistan Homo sapiens 9606 2023-08-26 CCHF/NIHPAK-19/2023 test_NIHPAK-19 "OR964915.1,OR964926.1,OR964937.1" diff --git a/kubernetes/loculus/templates/loculus-preprocessing-config.yaml b/kubernetes/loculus/templates/loculus-preprocessing-config.yaml index 08e748d642..1602ff0047 100644 --- a/kubernetes/loculus/templates/loculus-preprocessing-config.yaml +++ b/kubernetes/loculus/templates/loculus-preprocessing-config.yaml @@ -1,7 +1,11 @@ {{- range $organism, $organismConfig := (include "loculus.enabledOrganisms" . | fromJson) }} {{- $metadata := ($organismConfig.schema | include "loculus.patchMetadataSchema" | fromYaml).metadata }} -{{- $rawNucleotideSequences := $organismConfig.schema.nucleotideSequences }} -{{- $nucleotideSequencesList := (eq $rawNucleotideSequences nil | ternary (list "main") $rawNucleotideSequences) }} +{{- $referenceGenomes:= include "loculus.mergeReferenceGenomes" $organismConfig.referenceGenomes | fromYaml }} +{{- $genesList := (eq $referenceGenomes.genes nil | ternary (list) $referenceGenomes.genes) }} +{{- $genesDict := dict "genes" (list) -}} +{{- range $g := $genesList }} + {{- $_ := set $genesDict "genes" (append ($genesDict.genes) $g.name) -}} +{{- end }} {{- $flattened := include "loculus.flattenPreprocessingVersions" $organismConfig.preprocessing | fromJson }} {{- range $processingIndex, $processingConfig := $flattened.items }} {{- if $processingConfig.configFile }} @@ -17,7 +21,7 @@ data: preprocessing-config.yaml: | organism: {{ $organism }} {{- $preproAndEnaConfigFile | toYaml | nindent 4 }} - {{- (dict "nucleotideSequences" $nucleotideSequencesList) | toYaml | nindent 4 }} + {{- $genesDict | toYaml | nindent 4 }} processing_spec: {{- $args := dict "metadata" $metadata "referenceGenomes" $organismConfig.referenceGenomes }} {{- include "loculus.preprocessingSpecs" $args | nindent 6 }} diff --git a/kubernetes/loculus/values.schema.json b/kubernetes/loculus/values.schema.json index c3d4f7e25b..0f22052e10 100644 --- a/kubernetes/loculus/values.schema.json +++ b/kubernetes/loculus/values.schema.json @@ -604,7 +604,7 @@ "type": "boolean", "description": "If true run nextclade sort and require that the highest scoring match is in the config.accepted_dataset_matches" }, - "minimizer_url": { + "minimizer_index": { "groups": ["nextcladePipelineConfigFile"], "docsIncludePrefix": false, "type": "string", diff --git a/kubernetes/loculus/values.yaml b/kubernetes/loculus/values.yaml index d713b5b351..337d7db59b 100644 --- a/kubernetes/loculus/values.yaml +++ b/kubernetes/loculus/values.yaml @@ -1324,7 +1324,6 @@ defaultOrganismConfig: &defaultOrganismConfig replicas: 2 configFile: &preprocessingConfigFile log_level: DEBUG - genes: [] batch_size: 100 create_embl_file: true nextclade_dataset_server: https://data.clades.nextstrain.org/v3 @@ -1346,8 +1345,8 @@ defaultOrganisms: version: [1, 2] configFile: <<: *preprocessingConfigFile - genes: [NP, VP35, VP40, GP, sGP, ssGP, VP30, VP24, L] - nextclade_dataset_name: nextstrain/ebola/sudan + nucleotideSequences: + - nextclade_dataset_name: nextstrain/ebola/sudan nextclade_dataset_server: https://raw.githubusercontent.com/nextstrain/nextclade_data/ebola/data_output ingest: <<: *ingest @@ -1421,10 +1420,9 @@ defaultOrganisms: - <<: *preprocessing version: 1 configFile: - <<: *preprocessingConfigFile - nextclade_dataset_name: nextstrain/wnv/all-lineages + nucleotideSequences: + - nextclade_dataset_name: nextstrain/wnv/all-lineages nextclade_dataset_server: https://raw.githubusercontent.com/nextstrain/nextclade_data/wnv/data_output - genes: [capsid, prM, env, NS1, NS2A, NS2B, NS3, NS4A, 2K, NS4B, NS5] ingest: <<: *ingest configFile: @@ -1742,8 +1740,14 @@ defaultOrganisms: configFile: <<: *preprocessingConfigFile log_level: DEBUG - nextclade_dataset_name: community/pathoplexus/cchfv - genes: [RdRp, GPC, NP] + minimizer_index: https://loculus-project.github.io/nextclade-sort-minimizers/data/CCHF/minimizer.json + nucleotideSequences: + - name: L + nextclade_dataset_name: community/pathoplexus/cchfv/L + - name: M + nextclade_dataset_name: community/pathoplexus/cchfv/M + - name: S + nextclade_dataset_name: community/pathoplexus/cchfv/S ingest: <<: *ingest configFile: @@ -1857,10 +1861,22 @@ defaultOrganisms: - <<: *preprocessing configFile: <<: *preprocessingConfigFile - log_level: INFO - nextclade_dataset_name: community/hodcroftlab/enterovirus/enterovirus/linked + minimizer_index: "https://raw.githubusercontent.com/alejandra-gonzalezsanchez/loculus-evs/master/evs_minimizer-index.json" + nucleotideSequences: + - name: CV-A16 + nextclade_dataset_name: community/hodcroftlab/enterovirus/enterovirus/linked/CV-A16 + accepted_sort_matches: ["community/hodcroftlab/enterovirus/cva16", "community/hodcroftlab/enterovirus/enterovirus/linked/CV-A16"] + gene_prefix: "CV-A16-" + - name: CV-A10 + nextclade_dataset_name: community/hodcroftlab/enterovirus/enterovirus/linked/CV-A10 + gene_prefix: "CV-A10-" + - name: EV-A71 + nextclade_dataset_name: community/hodcroftlab/enterovirus/enterovirus/linked/EV-A71 + gene_prefix: "EV-A71-" + - name: EV-D68 + gene_prefix: "EV-D68-" + nextclade_dataset_name: community/hodcroftlab/enterovirus/enterovirus/linked/EV-D68 nextclade_dataset_server: https://raw.githubusercontent.com/alejandra-gonzalezsanchez/nextclade_data/multi-pathogen-evs/data_output - genes: ["CV-A16-VP4", "CV-A16-VP2", "CV-A16-VP3", "CV-A16-VP1", "CV-A16-2A", "CV-A16-2B", "CV-A16-2C", "CV-A16-3A", "CV-A16-3B", "CV-A16-3C", "CV-A16-3D", "CV-A10-VP4", "CV-A10-VP2", "CV-A10-VP3", "CV-A10-VP1", "CV-A10-2A", "CV-A10-2B", "CV-A10-2C", "CV-A10-3A", "CV-A10-3B", "CV-A10-3C", "CV-A10-3D", "EV-A71-VP4", "EV-A71-VP2", "EV-A71-VP3", "EV-A71-VP1", "EV-A71-2A", "EV-A71-2B", "EV-A71-2C", "EV-A71-3A", "EV-A71-3B", "EV-A71-3C", "EV-A71-3D", "EV-D68-VP4", "EV-D68-VP2", "EV-D68-VP3", "EV-D68-VP1", "EV-D68-2A", "EV-D68-2B", "EV-D68-2C", "EV-D68-3A", "EV-D68-3B", "EV-D68-3C", "EV-D68-3D"] ingest: <<: *ingest configFile: diff --git a/preprocessing/nextclade/src/loculus_preprocessing/config.py b/preprocessing/nextclade/src/loculus_preprocessing/config.py index b0b87bba42..9c70d676d6 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/config.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/config.py @@ -32,8 +32,21 @@ class AlignmentRequirement(StrEnum): # Determines whether ALL or ANY segments that a user provides must align. # ANY: warn if some segments fail and some segments align # ALL: error if any segment fails even if some segments align + # NONE: do not align any segments, just process them as-is + # - set if no nextclade dataset is provided ANY = "ANY" ALL = "ALL" + NONE = "NONE" + + +@dataclass +class NextcladeSequenceAndDataset: + name: str = "main" + nextclade_dataset_name: str | None = None + nextclade_dataset_tag: str | None = None + nextclade_dataset_server: str | None = None + accepted_sort_matches: list[str] = dataclasses.field(default_factory=list) + gene_prefix: str | None = None @dataclass @@ -53,21 +66,18 @@ class Config: organism: str = "mpox" genes: list[str] = dataclasses.field(default_factory=list) - nucleotideSequences: list[str] = dataclasses.field(default_factory=lambda: ["main"]) # noqa: N815 + nucleotideSequences: list[NextcladeSequenceAndDataset] = dataclasses.field( # noqa: N815 + default_factory=list + ) processing_spec: dict[str, dict[str, Any]] = dataclasses.field(default_factory=dict) multi_segment: bool = False alignment_requirement: AlignmentRequirement = AlignmentRequirement.ALL - nextclade_dataset_name: str | None = None - nextclade_dataset_name_map: dict[str, str] | None = None - nextclade_dataset_tag: str | None = None - nextclade_dataset_tag_map: dict[str, str] | None = None nextclade_dataset_server: str = "https://data.clades.nextstrain.org/v3" - nextclade_dataset_server_map: dict[str, str] | None = None require_nextclade_sort_match: bool = False - minimizer_url: str | None = None - accepted_dataset_matches: list[str] = dataclasses.field(default_factory=list) + minimizer_index: str | None = None + create_embl_file: bool = False scientific_name: str = "Orthonairovirus haemorrhagiae" molecule_type: MoleculeType = MoleculeType.GENOMIC_RNA @@ -79,6 +89,49 @@ class Config: ) +def get_accepted_sort_matches( + segment: NextcladeSequenceAndDataset, +) -> list[str]: + accepted_dataset_names = set() + + if segment.accepted_sort_matches: + accepted_dataset_names.update(segment.accepted_sort_matches) + if segment.nextclade_dataset_name: + accepted_dataset_names.add(segment.nextclade_dataset_name) + accepted_dataset_names.add(segment.name) + return list(accepted_dataset_names) + + +def assign_nextclade_sequence_and_dataset( + nuc_seq_values: list[dict[str, Any]], +) -> list[NextcladeSequenceAndDataset]: + if not isinstance(nuc_seq_values, list): + error_msg = f"nucleotideSequences should be a list of dicts, got: {type(nuc_seq_values)}" + logger.error(error_msg) + raise ValueError(error_msg) + nextclade_sequence_and_dataset_list: list[NextcladeSequenceAndDataset] = [] + for value in nuc_seq_values: + if value is None or not isinstance(value, dict): + continue + seq_and_dataset = NextcladeSequenceAndDataset() + for seq_key, seq_value in value.items(): + if hasattr(seq_and_dataset, seq_key) and seq_value is not None: + setattr(seq_and_dataset, seq_key, seq_value) + seq_and_dataset.accepted_sort_matches = get_accepted_sort_matches(seq_and_dataset) + nextclade_sequence_and_dataset_list.append(seq_and_dataset) + return nextclade_sequence_and_dataset_list + + +def set_alignment_requirement(config: Config) -> AlignmentRequirement: + need_nextclade_dataset: bool = False + for sequence in config.nucleotideSequences: + if sequence.nextclade_dataset_name: + need_nextclade_dataset = True + if not need_nextclade_dataset: + return AlignmentRequirement.NONE + return config.alignment_requirement + + def load_config_from_yaml(config_file: str, config: Config | None = None) -> Config: config = Config() if config is None else copy.deepcopy(config) with open(config_file, encoding="utf-8") as file: @@ -86,6 +139,9 @@ def load_config_from_yaml(config_file: str, config: Config | None = None) -> Con logger.debug(f"Loaded config from {config_file}: {yaml_config}") for key, value in yaml_config.items(): if value is not None and hasattr(config, key): + if key == "nucleotideSequences": + setattr(config, key, assign_nextclade_sequence_and_dataset(value)) + continue attr = getattr(config, key) if isinstance(attr, StrEnum): try: @@ -170,6 +226,8 @@ def get_config(config_file: str | None = None, ignore_args: bool = False) -> Con if not config.backend_host: # Set here so we can use organism config.backend_host = f"http://127.0.0.1:8079/{config.organism}" + config.alignment_requirement = set_alignment_requirement(config) + if len(config.nucleotideSequences) > 1: config.multi_segment = True diff --git a/preprocessing/nextclade/src/loculus_preprocessing/datatypes.py b/preprocessing/nextclade/src/loculus_preprocessing/datatypes.py index e9d7e7f137..740153dd81 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/datatypes.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/datatypes.py @@ -89,6 +89,14 @@ class UnprocessedEntry: FunctionArgs = dict[ArgName, ArgValue] +@dataclass +class SegmentAssignment: + unalignedNucleotideSequences: dict[SegmentName, NucleotideSequence | None] # noqa: N815 + sequenceNameToFastaHeaderMap: dict[SegmentName, str] # noqa: N815 + errors: list[ProcessingAnnotation] + warnings: list[ProcessingAnnotation] + + @dataclass class ProcessingSpec: inputs: FunctionInputs @@ -108,6 +116,7 @@ class UnprocessedAfterNextclade: nucleotideInsertions: dict[SegmentName, list[NucleotideInsertion]] # noqa: N815 alignedAminoAcidSequences: dict[GeneName, AminoAcidSequence | None] # noqa: N815 aminoAcidInsertions: dict[GeneName, list[AminoAcidInsertion]] # noqa: N815 + sequenceNameToFastaHeaderMap: dict[SegmentName, str] # noqa: N815 errors: list[ProcessingAnnotation] warnings: list[ProcessingAnnotation] @@ -126,6 +135,7 @@ class ProcessedData: nucleotideInsertions: dict[SegmentName, Any] # noqa: N815 alignedAminoAcidSequences: dict[GeneName, Any] # noqa: N815 aminoAcidInsertions: dict[GeneName, Any] # noqa: N815 + sequenceNameToFastaHeaderMap: dict[SegmentName, str] # noqa: N815 files: dict[str, list[FileIdAndName]] | None = None diff --git a/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py b/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py index 1fa1288735..1a1c0dd0ad 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py @@ -14,7 +14,7 @@ import pandas as pd from Bio import SeqIO -from .config import Config +from .config import AlignmentRequirement, Config, NextcladeSequenceAndDataset from .datatypes import ( AccessionVersion, Alerts, @@ -27,6 +27,7 @@ NucleotideSequence, ProcessingAnnotation, ProcessingAnnotationAlignment, + SegmentAssignment, SegmentName, UnprocessedAfterNextclade, UnprocessedEntry, @@ -80,11 +81,12 @@ def parse_nextclade_tsv( ], result_dir: str, config: Config, - segment: SegmentName, + sequence_and_dataset: NextcladeSequenceAndDataset, ) -> tuple[ defaultdict[AccessionVersion, defaultdict[GeneName, list[AminoAcidInsertion]]], defaultdict[AccessionVersion, defaultdict[SegmentName, list[NucleotideInsertion]]], ]: + segment = sequence_and_dataset.name with Path(result_dir + "/nextclade.tsv").open(encoding="utf-8") as nextclade_tsv: reader = csv.DictReader(nextclade_tsv, delimiter="\t") for row in reader: @@ -99,7 +101,12 @@ def parse_nextclade_tsv( continue gene, val = ins.split(":", maxsplit=1) if gene in config.genes: - amino_acid_insertions[id][gene].append(val) + gene_name = ( + sequence_and_dataset.gene_prefix + gene + if sequence_and_dataset.gene_prefix + else gene + ) + amino_acid_insertions[id][gene_name].append(val) else: logger.debug( "Note: Nextclade found AA insertion in gene missing from config in gene " @@ -135,37 +142,27 @@ def parse_nextclade_json( def run_sort( - result_file_dir: str, + result_file: str, input_file: str, - alerts: Alerts, config: Config, - segment: SegmentName, + nextclade_dataset_server: str, dataset_dir: str, -) -> Alerts: +) -> pd.DataFrame: """ Run nextclade - - use config.minimizer_url or default minimizer from nextclade server - - assert highest score is in config.accepted_dataset_matches - (default is nextclade_dataset_name) + - use config.minimizer_index or default minimizer from nextclade server """ - nextclade_dataset_name = get_nextclade_dataset_name(config, segment) - if not config.accepted_dataset_matches and not nextclade_dataset_name: - logger.warning("No nextclade dataset name or accepted dataset match list found in config") - return alerts - nextclade_dataset_server = get_nextclade_dataset_server(config, segment) - - if config.minimizer_url: + if config.minimizer_index: minimizer_file = dataset_dir + "/minimizer/minimizer.json" - accepted_dataset_names = config.accepted_dataset_matches or [nextclade_dataset_name] # type: ignore + test = nextclade_dataset_server == "TEST" - result_file = result_file_dir + "/sort_output.tsv" - command = [ + subprocess_args_with_emtpy_strings = [ "nextclade3", "sort", input_file, - "-m" if config.minimizer_url else "", - f"{minimizer_file}" if config.minimizer_url else "", + "-m" if config.minimizer_index else "", + f"{minimizer_file}" if config.minimizer_index else "", "--output-results-tsv", f"{result_file}", "--max-score-gap", @@ -175,18 +172,18 @@ def run_sort( "--min-hits", "2", "--all-matches", - "--server", - f"{nextclade_dataset_server}", + "--server" if not test else "", + f"{nextclade_dataset_server}" if not test else "", ] + subprocess_args = [arg for arg in subprocess_args_with_emtpy_strings if arg] - logger.debug(f"Running nextclade sort: {command}") + logger.debug(f"Running nextclade sort: {subprocess_args}") - exit_code = subprocess.run(command, check=False).returncode # noqa: S603 + exit_code = subprocess.run(subprocess_args, check=False).returncode # noqa: S603 if exit_code != 0: msg = f"nextclade sort failed with exit code {exit_code}" raise Exception(msg) - - df = pd.read_csv( + return pd.read_csv( result_file, sep="\t", dtype={ @@ -197,6 +194,43 @@ def run_sort( }, ) + +def check_nextclade_sort_matches( # noqa: PLR0913, PLR0917 + result_file_dir: str, + input_file: str, + alerts: Alerts, + config: Config, + sequence_and_dataset: NextcladeSequenceAndDataset, + dataset_dir: str, +) -> Alerts: + """ + Run nextclade sort + - assert highest score is in sequence_and_dataset.accepted_sort_matches + (default is nextclade_dataset_name) + """ + nextclade_dataset_name = sequence_and_dataset.nextclade_dataset_name + if not sequence_and_dataset.accepted_sort_matches and not nextclade_dataset_name: + logger.warning("No nextclade dataset name or accepted dataset match list found in config") + return alerts + nextclade_dataset_server = ( + sequence_and_dataset.nextclade_dataset_server or config.nextclade_dataset_server + ) + + accepted_dataset_names = ( + sequence_and_dataset.accepted_sort_matches + or [nextclade_dataset_name] # type: ignore + or [sequence_and_dataset.name] # type: ignore + ) + + result_file = result_file_dir + "/sort_output.tsv" + df = run_sort( + result_file, + input_file, + config, + nextclade_dataset_server, + dataset_dir, + ) + hits = df.dropna(subset=["score"]).sort_values("score", ascending=False) best_hits = hits.groupby("seqName", as_index=False).first() @@ -213,7 +247,7 @@ def run_sort( "Sequence does not appear to match reference, per `nextclade sort`. " "Double check you are submitting to the correct organism." ), - ), + ) ) for _, row in best_hits.iterrows(): @@ -224,7 +258,7 @@ def run_sort( ProcessingAnnotationAlignment, AnnotationSourceType.NUCLEOTIDE_SEQUENCE, message=( - f"This sequence best matches {row['dataset']}, " + f"Sequence best matches {row['dataset']}, " "a different organism than the one you are submitting to: " f"{config.organism}. It is therefore not possible to release. " "Contact the administrator if you think this message is an error." @@ -235,7 +269,262 @@ def run_sort( return alerts -def enrich_with_nextclade( # noqa: C901, PLR0912, PLR0914, PLR0915 +# TODO: running this for each sequence is inefficient, should be run once per batch +def assign_segment_with_nextclade_sort( + input_unaligned_sequences: dict[str, NucleotideSequence | None], + config: Config, + dataset_dir: str, +) -> SegmentAssignment: + """ + Run nextclade sort + - assert highest score is in sequence_and_dataset.accepted_sort_matches + (default is nextclade_dataset_name) + """ + errors = [] + warnings = [] + unaligned_nucleotide_sequences: dict[SegmentName, NucleotideSequence | None] = {} + nextclade_dataset_server = config.nextclade_dataset_server + has_duplicate_segments = False + has_missing_segments = False + + with TemporaryDirectory(delete=not config.keep_tmp_dir) as result_dir: + input_file = result_dir + "/input.fasta" + os.makedirs(os.path.dirname(input_file), exist_ok=True) + with open(input_file, "w", encoding="utf-8") as f: + for id, seq in input_unaligned_sequences.items(): + f.write(f">{id}\n") + f.write(f"{seq}\n") + + result_file = result_dir + "/sort_output.tsv" + df = run_sort( + result_file, + input_file, + config, + nextclade_dataset_server, + dataset_dir, + ) + + no_hits = df[df["score"].isna()] + hits = df.dropna(subset=["score"]).sort_values("score", ascending=False) + for seq_name in no_hits["seqName"].unique(): + if seq_name not in hits["seqName"].unique(): + msg = ( + f"Sequence with fasta header {seq_name} does not appear to match any reference for organism: " + f"{config.organism} per `nextclade sort`. " + f"Double check you are submitting to the correct organism." + ) + has_missing_segments = True + if config.alignment_requirement == AlignmentRequirement.ALL: + errors.append( + ProcessingAnnotation.from_single( + ProcessingAnnotationAlignment, + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + message=msg, + ) + ) + else: + warnings.append( + ProcessingAnnotation.from_single( + ProcessingAnnotationAlignment, + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + message=msg, + ) + ) + + best_hits = hits.groupby("seqName", as_index=False).first() + logger.info(f"Found hits: {best_hits['seqName'].tolist()}") + + sort_results_map: dict[SegmentName, list[str]] = {} + + for _, row in best_hits.iterrows(): + not_found = True + for segment in config.nucleotideSequences: + # TODO: need to check somewhere that accepted_sort_matches does not overlap across segments + if row["dataset"] in segment.accepted_sort_matches: + not_found = False + sort_results_map.setdefault(segment.name, []).append(row["seqName"]) + break + if not not_found: + continue + has_missing_segments = True + msg = ( + f"Sequence {row['seqName']} best matches {row['dataset']}, " + "which is currently not an accepted option for organism: " + f"{config.organism}. It is therefore not possible to release. " + "Contact the administrator if you think this message is an error." + ) + if config.alignment_requirement == AlignmentRequirement.ALL: + errors.append( + ProcessingAnnotation.from_single( + ProcessingAnnotationAlignment, + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + message=msg, + ) + ) + else: + warnings.append( + ProcessingAnnotation.from_single( + ProcessingAnnotationAlignment, + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + message=msg, + ) + ) + sequenceNameToFastaHeaderMap: dict[SegmentName, str] = {} + for segment_name, headers in sort_results_map.items(): + if len(headers) > 1: + msg = ( + f"Multiple sequences (with fasta headers: {', '.join(headers)}) align to " + f" {segment_name} - only one entry is allowed." + ) + has_duplicate_segments = True + errors.append( + ProcessingAnnotation.from_single( + ProcessingAnnotationAlignment, + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + message=msg, + ) + ) + continue + sequenceNameToFastaHeaderMap[segment_name] = headers[0] + unaligned_nucleotide_sequences[segment_name] = input_unaligned_sequences[headers[0]] + + if ( + len(unaligned_nucleotide_sequences) == 0 + and not has_duplicate_segments + and (not has_missing_segments or config.alignment_requirement == AlignmentRequirement.ANY) + ): + errors.append( + ProcessingAnnotation.from_single( + ProcessingAnnotationAlignment, + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + message="No sequence data could be classified - check you are submitting to the correct organism.", + ) + ) + + return SegmentAssignment( + unalignedNucleotideSequences=unaligned_nucleotide_sequences, + sequenceNameToFastaHeaderMap=sequenceNameToFastaHeaderMap, + errors=errors, + warnings=warnings, + ) + + +def assign_single_segment( + input_unaligned_sequences: dict[str, NucleotideSequence | None], + config: Config, +) -> SegmentAssignment: + errors: list[ProcessingAnnotation] = [] + warnings: list[ProcessingAnnotation] = [] + unaligned_nucleotide_sequences: dict[SegmentName, NucleotideSequence | None] = {} + sequenceNameToFastaHeaderMap: dict[SegmentName, str] = {} + if len(input_unaligned_sequences) > 1: + errors.append( + ProcessingAnnotation.from_single( + ProcessingAnnotationAlignment, + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + message=( + f"Multiple sequences: {list(input_unaligned_sequences.keys())} found in the" + f" input data, but organism: {config.organism} is single-segmented. " + "Please check that your metadata and sequences are annotated correctly." + "Each metadata entry should have a single corresponding fasta sequence " + "entry with the same submissionId." + ), + ) + ) + else: + fastaHeader, value = next(iter(input_unaligned_sequences.items())) + sequenceNameToFastaHeaderMap["main"] = fastaHeader + unaligned_nucleotide_sequences["main"] = value + return SegmentAssignment( + unalignedNucleotideSequences=unaligned_nucleotide_sequences, + sequenceNameToFastaHeaderMap=sequenceNameToFastaHeaderMap, + errors=errors, + warnings=warnings, + ) + + +def assign_segment_using_header( + input_unaligned_sequences: dict[str, NucleotideSequence | None], + config: Config, +) -> SegmentAssignment: + errors: list[ProcessingAnnotation] = [] + warnings: list[ProcessingAnnotation] = [] + unaligned_nucleotide_sequences: dict[SegmentName, NucleotideSequence | None] = {} + sequenceNameToFastaHeaderMap: dict[SegmentName, str] = {} + duplicate_segments = set() + if not config.nucleotideSequences: + return SegmentAssignment( + unalignedNucleotideSequences={}, + sequenceNameToFastaHeaderMap={}, + errors=errors, + warnings=warnings, + ) + if not config.multi_segment: + return assign_single_segment(input_unaligned_sequences, config) + for sequence_and_dataset in config.nucleotideSequences: + segment = sequence_and_dataset.name + unaligned_segment = [ + data + for data in input_unaligned_sequences + if re.match(segment + "$", data.split("_")[-1], re.IGNORECASE) + or re.match( + segment + "$", data, re.IGNORECASE + ) # backward compatibility allow only segment name in submission dict + ] + if len(unaligned_segment) > 1: + duplicate_segments.update(unaligned_segment) + errors.append( + ProcessingAnnotation.from_single( + ProcessingAnnotationAlignment, + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + message=( + f"Found multiple sequences with the same segment name: {segment}. " + "Each metadata entry can have multiple corresponding fasta sequence " + "entries with format _." + ), + ) + ) + elif len(unaligned_segment) == 1: + sequenceNameToFastaHeaderMap[segment] = unaligned_segment[0] + unaligned_nucleotide_sequences[segment] = input_unaligned_sequences[ + unaligned_segment[0] + ] + remaining_segments = ( + set(input_unaligned_sequences.keys()) + - set(sequenceNameToFastaHeaderMap.values()) + - duplicate_segments + ) + if len(remaining_segments) > 0: + errors.append( + ProcessingAnnotation.from_single( + ProcessingAnnotationAlignment, + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + message=( + f"Found sequences in the input data with segments that are not in the config: " + f"{', '.join(remaining_segments)}. " + "Each metadata entry can have multiple corresponding fasta sequence " + "entries with format _ valid segments are: " + f"{', '.join([sequence_and_dataset.name for sequence_and_dataset in config.nucleotideSequences])}." + ), + ) + ) + if len(unaligned_nucleotide_sequences) == 0 and not duplicate_segments: + errors.append( + ProcessingAnnotation.from_single( + ProcessingAnnotationAlignment, + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + message="No sequence data found - ", + ) + ) + return SegmentAssignment( + unalignedNucleotideSequences=unaligned_nucleotide_sequences, + sequenceNameToFastaHeaderMap=sequenceNameToFastaHeaderMap, + errors=errors, + warnings=warnings, + ) + + +def enrich_with_nextclade( # noqa: C901, PLR0914, PLR0915 unprocessed: Sequence[UnprocessedEntry], dataset_dir: str, config: Config ) -> dict[AccessionVersion, UnprocessedAfterNextclade]: """ @@ -249,11 +538,13 @@ def enrich_with_nextclade( # noqa: C901, PLR0912, PLR0914, PLR0915 nucleotideInsertions: dict[SegmentName, list[NucleotideInsertion]] alignedAminoAcidSequences: dict[GeneName, AminoAcidSequence | None] aminoAcidInsertions: dict[GeneName, list[AminoAcidInsertion]] + sequenceNameToFastaHeaderMap: dict[SegmentName, str] )` object. """ unaligned_nucleotide_sequences: dict[ AccessionVersion, dict[SegmentName, NucleotideSequence | None] ] = {} + segment_assignment_map: dict[AccessionVersion, dict[SegmentName, str]] = {} alerts: Alerts = Alerts() input_metadata: dict[AccessionVersion, dict[str, Any]] = {} aligned_aminoacid_sequences: dict[ @@ -266,52 +557,26 @@ def enrich_with_nextclade( # noqa: C901, PLR0912, PLR0914, PLR0915 id = entry.accessionVersion input_metadata[id] = entry.data.metadata input_metadata[id]["submitter"] = entry.data.submitter - input_metadata[id]["group_id"] = entry.data.group_id input_metadata[id]["submittedAt"] = entry.data.submittedAt + input_metadata[id]["group_id"] = entry.data.group_id aligned_aminoacid_sequences[id] = {} - unaligned_nucleotide_sequences[id] = {} aligned_nucleotide_sequences[id] = {} - alerts.warnings[id] = [] - alerts.errors[id] = [] - num_valid_segments = 0 - num_duplicate_segments = 0 - for segment in config.nucleotideSequences: - unaligned_segment = [ - data - for data in entry.data.unalignedNucleotideSequences - if re.match(segment + "$", data, re.IGNORECASE) - ] - if len(unaligned_segment) > 1: - num_duplicate_segments += len(unaligned_segment) - alerts.errors[id].append( - ProcessingAnnotation.from_single( - segment, - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - message="Found multiple sequences with the same segment name.", - ), - ) - elif len(unaligned_segment) == 1: - num_valid_segments += 1 - unaligned_nucleotide_sequences[id][segment] = ( - entry.data.unalignedNucleotideSequences[unaligned_segment[0]] - ) - aligned_nucleotide_sequences[id][segment] = None - if ( - len(entry.data.unalignedNucleotideSequences) - - num_valid_segments - - num_duplicate_segments - > 0 - ): - alerts.errors[id].append( - ProcessingAnnotation.from_single( - ProcessingAnnotationAlignment, - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - message=( - "Found unknown segments in the input data - " - "check your segments are annotated correctly." - ), - ), + segment_assignment_map[id] = {} + if not config.multi_segment: + segment_assignment = assign_single_segment( + input_unaligned_sequences=entry.data.unalignedNucleotideSequences, + config=config, + ) + else: + segment_assignment = assign_segment_with_nextclade_sort( + input_unaligned_sequences=entry.data.unalignedNucleotideSequences, + config=config, + dataset_dir=dataset_dir, ) + unaligned_nucleotide_sequences[id] = segment_assignment.unalignedNucleotideSequences + alerts.errors[id] = segment_assignment.errors + alerts.warnings[id] = segment_assignment.warnings + segment_assignment_map[id] = segment_assignment.sequenceNameToFastaHeaderMap nextclade_metadata: defaultdict[ AccessionVersion, defaultdict[SegmentName, dict[str, Any] | None] @@ -323,9 +588,12 @@ def enrich_with_nextclade( # noqa: C901, PLR0912, PLR0914, PLR0915 AccessionVersion, defaultdict[GeneName, list[AminoAcidInsertion]] ] = defaultdict(lambda: defaultdict(list)) with TemporaryDirectory(delete=not config.keep_tmp_dir) as result_dir: # noqa: PLR1702 - for segment in config.nucleotideSequences: - result_dir_seg = result_dir if segment == "main" else result_dir + "/" + segment - dataset_dir_seg = dataset_dir if segment == "main" else dataset_dir + "/" + segment + for sequence_and_dataset in config.nucleotideSequences: + segment = sequence_and_dataset.name + result_dir_seg = result_dir if not config.multi_segment else result_dir + "/" + segment + dataset_dir_seg = ( + dataset_dir if not config.multi_segment else dataset_dir + "/" + segment + ) input_file = result_dir_seg + "/input.fasta" os.makedirs(os.path.dirname(input_file), exist_ok=True) is_empty: bool = True @@ -339,7 +607,9 @@ def enrich_with_nextclade( # noqa: C901, PLR0912, PLR0914, PLR0915 continue if config.require_nextclade_sort_match: - alerts = run_sort(result_dir_seg, input_file, alerts, config, segment, dataset_dir) + alerts = check_nextclade_sort_matches( + result_dir_seg, input_file, alerts, config, sequence_and_dataset, dataset_dir + ) command = [ "nextclade3", @@ -377,7 +647,12 @@ def enrich_with_nextclade( # noqa: C901, PLR0912, PLR0914, PLR0915 masked_sequence = mask_terminal_gaps( str(aligned_sequence.seq), mask_char="X" ) - aligned_aminoacid_sequences[sequence_id][gene] = masked_sequence + gene_name = ( + sequence_and_dataset.gene_prefix + gene + if sequence_and_dataset.gene_prefix + else gene + ) + aligned_aminoacid_sequences[sequence_id][gene_name] = masked_sequence except FileNotFoundError: # TODO: Add warning to each sequence logger.info( @@ -390,7 +665,11 @@ def enrich_with_nextclade( # noqa: C901, PLR0912, PLR0914, PLR0915 result_dir_seg, nextclade_metadata, segment, unaligned_nucleotide_sequences ) # this includes the "annotation" field amino_acid_insertions, nucleotide_insertions = parse_nextclade_tsv( - amino_acid_insertions, nucleotide_insertions, result_dir_seg, config, segment + amino_acid_insertions, + nucleotide_insertions, + result_dir_seg, + config, + sequence_and_dataset, ) return { @@ -402,6 +681,7 @@ def enrich_with_nextclade( # noqa: C901, PLR0912, PLR0914, PLR0915 nucleotideInsertions=nucleotide_insertions[id], alignedAminoAcidSequences=aligned_aminoacid_sequences[id], aminoAcidInsertions=amino_acid_insertions[id], + sequenceNameToFastaHeaderMap=segment_assignment_map[id], errors=alerts.errors[id], warnings=alerts.warnings[id], ) @@ -429,37 +709,15 @@ def load_aligned_nuc_sequences( return aligned_nucleotide_sequences -def get_nextclade_dataset_name(config: Config, segment: SegmentName) -> str | None: - if config.nextclade_dataset_name_map and segment in config.nextclade_dataset_name_map: - return config.nextclade_dataset_name_map[segment] - if not config.nextclade_dataset_name: - return None - return ( - config.nextclade_dataset_name - if segment == "main" - else config.nextclade_dataset_name + "/" + segment - ) - - -def get_nextclade_dataset_server(config: Config, segment: SegmentName) -> str: - if config.nextclade_dataset_server_map and segment in config.nextclade_dataset_server_map: - return config.nextclade_dataset_server_map[segment] - return config.nextclade_dataset_server - - -def get_nextclade_dataset_tag(config: Config, segment: SegmentName) -> str | None: - if config.nextclade_dataset_tag_map and segment in config.nextclade_dataset_tag_map: - return config.nextclade_dataset_tag_map[segment] - return config.nextclade_dataset_tag - - def download_nextclade_dataset(dataset_dir: str, config: Config) -> None: - for segment in config.nucleotideSequences: - nextclade_dataset_name = get_nextclade_dataset_name(config, segment) - nextclade_dataset_server = get_nextclade_dataset_server(config, segment) - nextclade_dataset_tag = get_nextclade_dataset_tag(config, segment) + for sequence_and_dataset in config.nucleotideSequences: + name = sequence_and_dataset.name + nextclade_dataset_name = sequence_and_dataset.nextclade_dataset_name + nextclade_dataset_server = ( + sequence_and_dataset.nextclade_dataset_server or config.nextclade_dataset_server + ) - dataset_dir_seg = dataset_dir if segment == "main" else dataset_dir + "/" + segment + dataset_dir_seg = dataset_dir if not config.multi_segment else dataset_dir + "/" + name dataset_download_command = [ "nextclade3", "dataset", @@ -469,8 +727,8 @@ def download_nextclade_dataset(dataset_dir: str, config: Config) -> None: f"--output-dir={dataset_dir_seg}", ] - if nextclade_dataset_tag is not None: - dataset_download_command.append(f"--tag={nextclade_dataset_tag}") + if sequence_and_dataset.nextclade_dataset_tag is not None: + dataset_download_command.append(f"--tag={sequence_and_dataset.nextclade_dataset_tag}") logger.info("Downloading Nextclade dataset: %s", dataset_download_command) if subprocess.run(dataset_download_command, check=False).returncode != 0: # noqa: S603 diff --git a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py index 5fb711156c..aa5b39a2a1 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py @@ -43,6 +43,7 @@ ) from .embl import create_flatfile from .nextclade import ( + assign_segment_using_header, download_nextclade_dataset, enrich_with_nextclade, ) @@ -202,6 +203,7 @@ def processed_entry_no_alignment( output_metadata: ProcessedMetadata, errors: list[ProcessingAnnotation], warnings: list[ProcessingAnnotation], + sequenceNameToFastaHeaderMap: dict[SegmentName, str], ) -> SubmissionData: """Process a single sequence without alignment""" @@ -221,6 +223,7 @@ def processed_entry_no_alignment( nucleotideInsertions=nucleotide_insertions, alignedAminoAcidSequences=aligned_aminoacid_sequences, aminoAcidInsertions=amino_acid_insertions, + sequenceNameToFastaHeaderMap=sequenceNameToFastaHeaderMap, ), errors=errors, warnings=warnings, @@ -329,7 +332,7 @@ def get_output_metadata( ) continue - if output_field.startswith("length_") and output_field[7:] in config.nucleotideSequences: + if output_field.startswith("length_") and output_field[7:] in [seq.name for seq in config.nucleotideSequences]: segment = output_field[7:] output_metadata[output_field] = get_sequence_length( unprocessed.unalignedNucleotideSequences, segment @@ -387,15 +390,6 @@ def alignment_errors_warnings( ) -> tuple[list[ProcessingAnnotation], list[ProcessingAnnotation]]: errors: list[ProcessingAnnotation] = [] warnings: list[ProcessingAnnotation] = [] - if not any(unprocessed.unalignedNucleotideSequences.values()): - errors.append( - ProcessingAnnotation.from_single( - ProcessingAnnotationAlignment, - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - message="No sequence data found - check segments are annotated correctly", - ) - ) - return (errors, warnings) if not unprocessed.nextcladeMetadata and unprocessed.unalignedNucleotideSequences: message = ( "An unknown internal error occurred while aligning sequences, " @@ -408,7 +402,8 @@ def alignment_errors_warnings( ) return (errors, warnings) aligned_segments = set() - for segment in config.nucleotideSequences: + for sequence_and_dataset in config.nucleotideSequences: + segment = sequence_and_dataset.name if segment not in unprocessed.unalignedNucleotideSequences: continue if unprocessed.nextcladeMetadata and ( @@ -432,6 +427,7 @@ def alignment_errors_warnings( if ( not aligned_segments + and unprocessed.unalignedNucleotideSequences and config.multi_segment and config.alignment_requirement == AlignmentRequirement.ANY ): @@ -449,7 +445,8 @@ def unpack_annotations(config, nextclade_metadata: dict[str, Any] | None) -> dic if not config.create_embl_file or not nextclade_metadata: return None annotations: dict[str, Any] = {} - for segment in config.nucleotideSequences: + for sequence_and_dataset in config.nucleotideSequences: + segment = sequence_and_dataset.name if segment in nextclade_metadata: annotations[segment] = None if nextclade_metadata[segment]: @@ -484,6 +481,7 @@ def process_single( nucleotideInsertions=unprocessed.nucleotideInsertions, alignedAminoAcidSequences=unprocessed.alignedAminoAcidSequences, aminoAcidInsertions=unprocessed.aminoAcidInsertions, + sequenceNameToFastaHeaderMap=unprocessed.sequenceNameToFastaHeaderMap, ), errors=list(set(unprocessed.errors + iupac_errors + alignment_errors + metadata_errors)), warnings=list(set(unprocessed.warnings + alignment_warnings + metadata_warnings)), @@ -503,6 +501,11 @@ def process_single_unaligned( config: Config, ) -> SubmissionData: """Process a single sequence per config""" + segment_assignment = assign_segment_using_header( + input_unaligned_sequences=unprocessed.unalignedNucleotideSequences, + config=config, + ) + unprocessed.unalignedNucleotideSequences = segment_assignment.unalignedNucleotideSequences iupac_errors = errors_if_non_iupac(unprocessed.unalignedNucleotideSequences) output_metadata, metadata_errors, metadata_warnings = get_output_metadata( @@ -513,8 +516,9 @@ def process_single_unaligned( accession_version=accession_version, unprocessed=unprocessed, output_metadata=output_metadata, - errors=list(set(iupac_errors + metadata_errors)), + errors=list(set(iupac_errors + metadata_errors + segment_assignment.errors)), warnings=list(set(metadata_warnings)), + sequenceNameToFastaHeaderMap=segment_assignment.sequenceNameToFastaHeaderMap, ) @@ -530,6 +534,7 @@ def processed_entry_with_errors(id) -> SubmissionData: nucleotideInsertions=defaultdict(dict[str, Any]), alignedAminoAcidSequences=defaultdict(dict[str, Any]), aminoAcidInsertions=defaultdict(dict[str, Any]), + sequenceNameToFastaHeaderMap=defaultdict(str), ), errors=[ ProcessingAnnotation.from_single( @@ -552,7 +557,7 @@ def process_all( ) -> Sequence[SubmissionData]: processed_results = [] logger.debug(f"Processing {len(unprocessed)} unprocessed sequences") - if config.nextclade_dataset_name: + if config.alignment_requirement != AlignmentRequirement.NONE: nextclade_results = enrich_with_nextclade(unprocessed, dataset_dir, config) for id, result in nextclade_results.items(): try: @@ -610,10 +615,10 @@ def upload_flatfiles(processed: Sequence[SubmissionData], config: Config) -> Non def run(config: Config) -> None: with TemporaryDirectory(delete=not config.keep_tmp_dir) as dataset_dir: - if config.nextclade_dataset_name: + if config.alignment_requirement != AlignmentRequirement.NONE: download_nextclade_dataset(dataset_dir, config) - if config.minimizer_url and config.require_nextclade_sort_match: - download_minimizer(config.minimizer_url, dataset_dir + "/minimizer/minimizer.json") + if config.minimizer_index: + download_minimizer(config.minimizer_index, dataset_dir + "/minimizer/minimizer.json") total_processed = 0 etag = None last_force_refresh = time.time() diff --git a/preprocessing/nextclade/tests/ebola-dataset/minimizer/minimizer.json b/preprocessing/nextclade/tests/ebola-dataset/minimizer/minimizer.json new file mode 100644 index 0000000000..81b74b890b --- /dev/null +++ b/preprocessing/nextclade/tests/ebola-dataset/minimizer/minimizer.json @@ -0,0 +1 @@ +{"schemaVersion": "3.0.0", "version": "1", "params": {"k": 17, "cutoff": 2147483648}, "minimizers": {"69543": [0, 1], "71846": [0, 1], "170137": [0], "390225": [0], "695790": [0], "891801": [0], "1021124": [0], "1236686": [0], "1315823": [0], "1361598": [0], "1489080": [0], "1638488": [0], "1712786": [0], "1744960": [0], "2205906": [0], "2207683": [0], "2232227": [0], "2256261": [0], "2314094": [0], "2657358": [0], "2860514": [0], "3019789": [0], "3332777": [0, 1], "3374274": [0], "3381333": [0], "3519654": [0], "3618250": [0], "4380772": [0], "4790460": [0], "5017706": [0], "5817014": [0], "5842603": [0], "5856566": [0, 1], "5965758": [0], "6152586": [0], "6230241": [0], "6237563": [0], "6657098": [0], "6834193": [0], "6997452": [0], "7199056": [0], "7264803": [0], "7588308": [0], "7657028": [0], "7956217": [0], "8076853": [0], "8177912": [0], "8239893": [0], "8507894": [0], "8683684": [0], "8694590": [0], "8913021": [0], "9103477": [0], "9274733": [0], "9355036": [0], "9580612": [0], "9682087": [0], "9706355": [0], "9712770": [0], "9884429": [0], "9890357": [0], "9928977": [0], "10045824": [0], "10224249": [0], "10237242": [0], "10244336": [0], "10274412": [0], "10397189": [0], "10892876": [0], "10919435": [0], "10964485": [0, 1], "10985532": [0], "11706086": [0], "11946366": [0], "12487294": [0], "12597367": [0], "12687721": [0], "13218851": [0], "13353260": [0], "13497653": [0], "13752789": [0], "14231513": [0], "14287530": [0], "14412255": [0], "14475675": [0], "14761505": [0], "15079688": [0], "15108615": [0], "15442741": [0], "15506579": [0], "15619082": [0], "15670607": [0], "15705850": [0], "15773812": [0], "16097971": [0], "16102956": [0], "16362216": [0], "16662114": [0], "17416671": [0], "17607948": [0], "17668446": [0], "17691960": [0], "17904642": [0], "17950997": [0], "17972344": [0], "18010866": [0], "18044147": [0], "18050114": [0], "18057238": [0], "18220116": [0], "18287385": [0], "18323168": [0], "18535032": [0], "18757419": [0], "18768739": [0], "18792955": [0], "18945047": [0], "19129826": [0, 1], "19301390": [0], "19337400": [0], "19508452": [0], "19761131": [0, 1], "19813761": [0], "20045323": [0], "20269901": [0], "20547441": [0], "20548548": [0], "20586755": [0], "20739471": [0], "21526564": [0], "21573297": [0], "21894951": [0], "22006798": [0], "22029184": [0], "22173177": [0], "22183178": [0], "22307098": [0], "22652255": [0], "22701650": [0, 1], "22748139": [0], "23035451": [0], "23159688": [0], "23185800": [0], "23613382": [0], "23898180": [0], "23963613": [0], "24112430": [0], "24251239": [0], "24258613": [0], "24364498": [0], "24415031": [0], "24459412": [0], "24645190": [0], "24707454": [0], "25353425": [0], "25411810": [0], "25464423": [0], "25555172": [0], "25854453": [0], "25865656": [0], "25966565": [0], "26893652": [0], "27064641": [0], "27085654": [0], "27753387": [0, 1], "27900659": [0], "27930358": [0], "28094088": [0], "28142437": [0], "28616334": [0], "29237303": [0], "29297884": [0], "29557555": [0], "29836036": [0], "29844752": [0], "30182111": [0], "30375354": [0], "30458268": [0], "30464050": [0], "31180675": [0], "31310114": [0], "31478204": [0], "31990850": [0], "32480029": [0], "32534490": [0], "32676233": [0, 1], "33262933": [0], "33364701": [0], "33723057": [0], "33814654": [0], "33877493": [0], "33947092": [0], "33989823": [0], "34254946": [0], "34292914": [0], "34460235": [0, 1], "34479366": [0], "34529743": [0], "35102265": [0, 1], "35174403": [0], "35305718": [0], "35333920": [0], "35347717": [0], "35489432": [0, 1], "35544693": [0], "35946090": [0], "35990473": [0], "36240733": [0], "36307880": [0], "36611787": [0], "36958878": [0], "37176964": [0], "37181559": [0], "37341339": [0], "38184768": [0], "38436357": [0], "39235001": [0], "39632772": [0], "39790525": [0], "39867039": [0], "40211608": [0, 1], "40483052": [0, 1], "40508648": [0], "40587990": [0], "40676748": [0], "40784127": [0], "41307635": [0], "41505281": [0], "41884625": [0], "42253443": [0], "42946366": [0], "43085590": [0], "43116533": [0], "43133119": [0], "43939480": [0], "44433940": [0], "44488128": [0], "44972911": [0], "45278921": [0], "45914670": [0], "46383787": [0], "47261906": [0], "47274968": [0], "47439279": [0], "47844668": [0], "48035742": [0], "48281152": [0], "48412392": [0], "48687809": [0], "48696913": [0], "49168102": [0], "49423003": [0], "49435284": [0], "50047573": [0], "50055451": [0], "50084114": [0], "50086011": [0], "50096172": [0], "50148228": [0], "50233467": [0], "50404434": [0], "50428924": [0], "50449420": [0], "50574421": [0], "50608863": [0], "50928235": [0], "51281015": [0], "51323789": [0], "51606627": [0], "52127193": [0], "52867887": [0], "53246391": [0], "53356726": [0], "53497252": [0], "53528362": [0], "53535733": [0], "53601384": [0], "53707058": [0], "53828235": [0], "53871296": [0], "53902110": [0], "53969467": [0], "53975111": [0], "54157406": [0], "54589203": [0], "55015701": [0], "55116443": [0], "55234335": [0], "55290593": [0], "55301817": [0], "55400808": [0], "55428353": [0], "55539450": [0], "55835498": [0], "56261291": [0], "56359438": [0], "56375303": [0], "56402582": [0], "56467036": [0], "56511892": [0], "56719098": [0], "56831568": [0], "57087051": [0], "57547262": [0, 1], "57553100": [0], "57619838": [0, 1], "57806409": [0], "57951909": [0], "57968728": [0], "58076188": [0], "58259379": [0], "58410437": [0], "58846494": [0], "58906697": [0], "59017963": [0], "59216573": [0], "59360905": [0], "59513365": [0], "60407927": [0], "60773760": [0], "61908786": [0], "61969634": [0], "62063872": [0], "62368719": [0], "62448674": [0], "62474454": [0], "62502089": [0], "62649451": [0], "63245138": [0], "63651448": [0], "63732755": [0], "63801062": [0], "63810175": [0], "63850985": [0], "63952575": [0], "64103807": [0], "64259553": [0], "64432570": [0, 1], "64541070": [0, 1], "64656469": [0], "64934740": [0], "64979718": [0], "65610958": [0], "66132123": [0], "66788038": [0], "67080539": [0], "67083513": [0], "67286384": [0], "67470147": [0], "67611961": [0], "67961354": [0], "68278838": [0], "68356024": [0], "68396843": [0], "68488282": [0], "68517268": [0], "69215307": [0], "69460330": [0], "69471371": [0], "70092748": [0], "70333009": [0], "70838054": [0], "70839258": [0], "71211071": [0], "71594703": [0], "71692535": [0], "71888511": [0], "72046330": [0], "72057275": [0], "72277461": [0], "72303593": [0], "72809286": [0], "72901368": [0], "73035753": [0], "73475041": [0], "74081150": [0], "74214451": [0], "74832705": [0], "74951715": [0], "74980869": [0], "75335614": [0], "75543885": [0], "75617822": [0], "76359200": [0], "76782045": [0], "77020029": [0, 1], "77219125": [0], "77389005": [0], "77539036": [0], "77603442": [0], "77816402": [0], "77824732": [0], "78154680": [0], "78708336": [0], "78976570": [0], "79238622": [0], "79392112": [0, 1], "79655443": [0], "79680803": [0], "79729997": [0], "79779982": [0], "80072921": [0], "80292015": [0], "80485899": [0], "80871155": [0], "81592111": [0], "81924676": [0], "81966046": [0], "82089315": [0], "82330633": [0], "82367068": [0, 1], "83476851": [0], "83727619": [0], "84191558": [0, 1], "84741415": [0], "85669795": [0], "86143810": [0], "86381903": [0], "86809978": [0], "86863956": [0], "87029365": [0], "87451653": [0], "87523522": [0], "87897808": [0], "88032659": [0], "88163076": [0], "88293442": [0, 1], "88357073": [0], "88385644": [0], "88608574": [0, 1], "88743670": [0], "88882525": [0], "89096714": [0], "89440112": [0], "89445250": [0, 1], "90138369": [0], "90375986": [0], "91072849": [0], "91544522": [0], "91662923": [0], "91725078": [0], "91861461": [0], "91889346": [0, 1], "91948592": [0], "92548259": [0], "92608258": [0], "92625291": [0], "92708763": [0], "93098758": [0], "93259197": [0], "93502132": [0, 1], "93814560": [0], "93975590": [0], "94180578": [0], "94383318": [0], "94508326": [0], "94535326": [0], "94918204": [0], "95004933": [0], "95090617": [0], "95629285": [0], "95658449": [0], "95661961": [0], "95773734": [0], "95841472": [0], "95880833": [0], "95921832": [0], "95984643": [0], "96047303": [0], "96047458": [0], "96116499": [0], "96506738": [0], "96810400": [0], "97056673": [0], "97581329": [0], "97661046": [0], "97689711": [0], "98121788": [0], "98206218": [0], "98609481": [0], "99062333": [0], "99304148": [0], "99505139": [0], "99730286": [0], "99795431": [0], "100349939": [0], "100370148": [0, 1], "100533946": [0], "100548484": [0], "100669116": [0], "100885590": [0, 1], "101046463": [0, 1], "101134445": [0], "101177795": [0], "101433491": [0], "102018900": [0], "102035626": [0], "102510372": [0], "102599448": [0], "103109886": [0], "103302847": [0], "103315015": [0], "103795110": [0], "103997067": [0], "104128829": [0], "104463608": [0], "104744177": [0, 1], "104832523": [0], "104982140": [0], "105008139": [0], "105229283": [0], "105610419": [0], "105653665": [0], "105851049": [0], "105865211": [0], "106183930": [0], "106488567": [0], "106597809": [0], "106841242": [0], "107116489": [0, 1], "107160485": [0], "107177143": [0], "108046155": [0], "108300895": [0], "108575709": [0], "108838925": [0], "108971462": [0], "109074345": [0], "109404405": [0], "109456426": [0], "109597253": [0], "109638106": [0], "110027175": [0], "110035827": [0], "110058939": [0], "110207800": [0], "110316206": [0], "110340085": [0], "110589546": [0], "111118495": [0], "111522738": [0], "111616208": [0], "112226235": [0], "112631249": [0], "112631253": [0], "112853151": [0], "112950753": [0], "113204767": [0], "113208379": [0], "113278875": [0], "113381009": [0], "113408217": [0], "113712658": [0, 1], "113873240": [0], "113921140": [0], "114285740": [0], "114798144": [0], "115008572": [0], "115024820": [0], "115534789": [0], "115654667": [0], "116156432": [0], "116230993": [0], "116376102": [0], "117507070": [0], "117521197": [0, 1], "117630085": [0], "117745821": [0], "118381504": [0], "118883569": [0], "119250666": [0], "119271174": [0], "119320795": [0], "120164431": [0], "120354822": [0], "120373041": [0], "120555881": [0], "120769550": [0], "121380561": [0], "121686558": [0], "121687906": [0], "122180050": [0], "123254987": [0], "123386420": [0], "123392938": [0], "124319529": [0], "124524725": [0], "124593151": [0], "124734788": [0], "125525720": [0], "125925654": [0], "126265014": [0], "126308982": [0], "126457880": [0], "126498179": [0], "126697486": [0], "126873114": [0], "127240338": [0], "127240647": [0], "127320245": [0], "127407239": [0], "127599142": [0], "127677548": [0], "127998637": [0], "128093092": [0], "128838055": [0], "129015209": [0], "129548298": [0], "129651254": [0], "129677771": [0], "129831409": [0], "130574023": [0], "131076951": [0], "131083429": [0], "131471224": [0], "131484759": [0], "131528566": [0], "131942487": [0], "131980592": [0], "132061195": [0], "132397025": [0], "132545386": [0], "132550228": [0], "133254230": [0], "133420041": [0], "134199756": [0], "134232972": [0], "134559009": [0], "134820215": [0], "135059528": [0], "135061757": [0], "135102132": [0], "135221418": [0], "135520416": [0], "135683155": [0], "135892683": [0], "136105253": [0], "136193987": [0], "136309032": [0], "136641639": [0], "136654782": [0], "136685143": [0], "137053385": [0], "137191767": [0], "137212189": [0, 1], "137598969": [0], "137811805": [0], "138033143": [0], "138349455": [0], "138613330": [0], "138880282": [0], "139335971": [0], "139369208": [0], "139593067": [0], "139728151": [0], "139756203": [0], "140010452": [0], "140196505": [0], "140307113": [0], "141081855": [0], "141458159": [0], "141541801": [0], "141988965": [0], "141995694": [0, 1], "142308538": [0], "142845339": [0], "142864137": [0], "143085770": [0], "143128189": [0], "143193237": [0], "143453019": [0], "143541595": [0], "143688988": [0], "143826658": [0], "143835654": [0], "143839194": [0], "144258997": [0], "144898971": [0], "145229905": [0], "145324269": [0], "145335475": [0], "145483865": [0], "145629925": [0], "145750995": [0], "145942740": [0, 1], "146247439": [0], "146339141": [0], "146634714": [0], "146819826": [0], "146898050": [0], "147119697": [0], "147508565": [0], "147917614": [0], "147965352": [0], "148247440": [0], "148834296": [0], "149122423": [0, 1], "149189904": [0], "149360275": [0], "149383474": [0], "149609885": [0], "149654056": [0], "150140148": [0], "150191321": [0], "150425411": [0, 1], "150637385": [0], "150723583": [0], "150884197": [0], "150892711": [0], "150983450": [0], "151288764": [0], "151522450": [0], "151811731": [0], "152336660": [0], "153226049": [0], "153655716": [0], "154060701": [0], "154188563": [0], "155070812": [0], "155196881": [0], "155663213": [0], "155676508": [0], "155848723": [0], "155944529": [0], "156463006": [0], "156823116": [0], "157127493": [0], "157129988": [0], "157307253": [0], "157321948": [0], "157681116": [0, 1], "157861943": [0], "159506800": [0], "159556697": [0], "160109008": [0], "160130767": [0], "160325404": [0], "160373788": [0], "160541634": [0], "160569021": [0], "160717196": [0], "160927683": [0], "161050728": [0], "161241974": [0], "162161964": [0], "162540155": [0], "162611348": [0], "163226212": [0], "163605946": [0], "163618296": [0], "164011773": [0, 1], "164368195": [0], "164654107": [0], "164753321": [0], "164875397": [0], "165078621": [0], "165304681": [0], "165690143": [0, 1], "165789986": [0], "166010412": [0], "166062369": [0], "166091309": [0], "166158641": [0], "166174607": [0], "166278807": [0], "166447704": [0], "166674515": [0], "167593823": [0], "168345856": [0], "168875193": [0], "168944283": [0], "169019612": [0], "169539025": [0], "169721645": [0], "169842354": [0], "170048079": [0], "170153486": [0], "170293590": [0], "170309462": [0], "170442381": [0], "170767675": [0, 1], "170806269": [0], "171732287": [0], "172044093": [0], "172082513": [0], "172487309": [0], "173243223": [0], "173576921": [0], "173819840": [0], "173877926": [0], "173891774": [0], "174101712": [0], "174136721": [0], "174303291": [0], "174309748": [0], "174891977": [0], "174897990": [0], "174937499": [0], "175134599": [0], "175166118": [0], "175553542": [0, 1], "175627607": [0], "175733512": [0], "176276831": [0], "176844787": [0], "177278307": [0], "177885229": [0], "178254118": [0], "178664776": [0], "179076273": [0], "179335359": [0], "179624277": [0], "179645107": [0], "180129928": [0], "180226669": [0], "180253465": [0], "180536331": [0], "180552645": [0], "180706270": [0], "181962083": [0], "182024256": [0, 1], "182186150": [0], "182231949": [0], "182240277": [0], "182494695": [0], "182684611": [0], "183302253": [0, 1], "183850225": [0], "183858744": [0], "183979076": [0], "184324582": [0], "184353725": [0], "184418769": [0], "184423415": [0], "184495374": [0], "185184341": [0], "186456054": [0], "186845740": [0], "187103484": [0], "187236137": [0], "187238383": [0], "187387879": [0, 1], "187520614": [0, 1], "187520695": [0], "187663381": [0], "188029221": [0], "188111702": [0], "188380727": [0], "188435550": [0], "188484815": [0], "188487551": [0], "188530035": [0], "188627863": [0], "188703128": [0], "188733542": [0], "188879517": [0], "188937354": [0], "189490976": [0], "189569329": [0], "189720889": [0], "189776915": [0], "190314941": [0, 1], "190714747": [0], "190765889": [0], "191418137": [0], "191711870": [0], "191825896": [0], "191849389": [0], "191935754": [0], "192072462": [0], "192154944": [0], "192157033": [0], "192162825": [0], "192355943": [0], "192565040": [0], "192981315": [0], "193098259": [0], "193134063": [0], "193160769": [0], "193281754": [0], "193866995": [0], "193882752": [0], "193889937": [0], "194113057": [0], "194155133": [0], "194402200": [0], "194540163": [0], "194905876": [0], "195153361": [0], "195220823": [0], "195437542": [0], "195475810": [0], "195640721": [0], "195965153": [0], "196157891": [0], "196185012": [0], "196193407": [0], "196880716": [0], "196918216": [0], "196923654": [0], "196931352": [0], "197040396": [0], "197144639": [0], "197404796": [0], "197504890": [0], "198163342": [0], "198583663": [0], "198832577": [0], "199092559": [0], "199187949": [0], "199371780": [0], "199720838": [0], "199732294": [0], "199980808": [0], "200076790": [0], "200217000": [0], "200244877": [0], "200288442": [0], "200481027": [0], "200820155": [0], "201045422": [0], "201210045": [0], "202164581": [0], "202331432": [0], "202396622": [0], "202605705": [0], "203234249": [0], "203314388": [0], "203581458": [0], "203790614": [0], "203921386": [0], "204226888": [0], "204314158": [0], "204420730": [0], "204452669": [0], "204498228": [0], "205194907": [0], "205269412": [0], "205362561": [0], "206320241": [0], "207366784": [0], "207451307": [0], "207502208": [0], "207612468": [0], "207689809": [0], "208397277": [0], "208631139": [0], "208900781": [0], "209420971": [0], "209649188": [0], "209736590": [0], "210035596": [0], "210038522": [0], "210217696": [0], "210420339": [0], "210660656": [0], "211284092": [0], "211298879": [0], "211686805": [0], "212208874": [0], "212437737": [0], "212741246": [0], "212764298": [0], "212888265": [0], "212946296": [0], "212981310": [0], "213014756": [0], "213041103": [0], "213152644": [0], "213159844": [0], "214526307": [0, 1], "214669699": [0], "215468875": [0], "215875979": [0], "215940619": [0], "216124109": [0], "216162245": [0], "216376085": [0], "217020514": [0], "217497013": [0], "217596884": [0], "217609760": [0], "217673320": [0], "217853086": [0], "217986529": [0], "218081989": [0], "218274326": [0], "219232505": [0], "219257491": [0], "219495016": [0], "219615655": [0], "219755092": [0], "219906968": [0], "219977921": [0], "220066254": [0], "220079933": [0], "220384660": [0], "220821630": [0], "221470164": [0], "221480000": [0], "221517127": [0], "221691034": [0], "221740659": [0, 1], "222393426": [0], "222864465": [0], "223411830": [0], "223685233": [0], "223780888": [0], "223808164": [0, 1], "224705755": [0], "224807576": [0], "224856075": [0, 1], "225143660": [0], "225311424": [0], "225338308": [0], "225453583": [0], "225649876": [0], "226112611": [0], "226297851": [0], "226387447": [0], "226725799": [0], "227139806": [0], "227199668": [0], "227607606": [0], "227681053": [0], "228309134": [0], "228323721": [0], "228406233": [0], "228685927": [0, 1], "228785170": [0], "228935114": [0], "229052691": [0], "229271971": [0], "229568793": [0], "229739542": [0], "229921501": [0], "230054081": [0], "230093197": [0], "231811352": [0], "231904756": [0], "231975834": [0], "232066603": [0], "232070266": [0], "232221050": [0], "232301673": [0], "232826106": [0], "233038718": [0], "233219313": [0], "233267889": [0], "233781459": [0], "233824620": [0], "234513098": [0], "234916749": [0], "234956373": [0], "235120714": [0], "235391449": [0], "235902808": [0], "236156589": [0], "236215057": [0, 1], "236555806": [0], "236711435": [0], "236991547": [0], "237303559": [0], "237527901": [0], "237724446": [0], "237839598": [0], "238080081": [0], "238293219": [0], "238457947": [0], "238887681": [0], "238913900": [0], "238967025": [0], "238969855": [0], "239085833": [0], "239088971": [0], "239305801": [0], "239458435": [0], "239597874": [0], "239818040": [0], "239870220": [0], "240200037": [0], "240285750": [0], "240523264": [0], "240529529": [0], "240909726": [0], "241067995": [0], "241313001": [0], "241923292": [0], "242154993": [0], "242182807": [0], "242383497": [0], "242744640": [0], "242938111": [0], "243851132": [0], "244092686": [0], "244226618": [0], "244980523": [0, 1], "245446096": [0], "245492599": [0], "245564441": [0], "245678848": [0], "245776254": [0], "245921921": [0], "245989638": [0], "246620254": [0], "246760350": [0], "247296516": [0], "247565746": [0], "247885726": [0], "248091596": [0], "248120843": [0], "249049538": [0], "249553077": [0], "249596575": [0], "249837987": [0], "249841037": [0], "250156439": [0, 1], "250164185": [0], "250914859": [0], "250924063": [0], "250934734": [0], "251026124": [0], "251067944": [0], "252161994": [0], "252289661": [0], "252356441": [0], "252379294": [0], "252822148": [0], "252947754": [0], "253076351": [0], "253277461": [0], "253351345": [0], "254002287": [0], "254536115": [0], "254538885": [0], "254592282": [0], "255252600": [0], "255345993": [0], "255408394": [0], "255503272": [0], "255551085": [0], "255732947": [0], "255816963": [0], "256021381": [0], "256162456": [0, 1], "256292284": [0], "256417096": [0], "256584478": [0], "256649996": [0], "256763176": [0], "256768895": [0], "256774758": [0], "257042821": [0], "257137837": [0], "257165337": [0], "257301968": [0, 1], "257453874": [0], "257554948": [0], "257647121": [0], "257669749": [0], "258249080": [0], "258427558": [0], "258874562": [0], "259428476": [0], "259687609": [0], "260015600": [0], "260245278": [0], "260752225": [0], "260999554": [0], "261375597": [0], "261390048": [0], "262077795": [0], "262247239": [0], "262247361": [0], "262306861": [0], "262562180": [0, 1], "262745488": [0], "263100605": [0], "263245189": [0], "263298049": [0], "263828151": [0], "264613942": [0], "264651246": [0], "264798983": [0], "264950442": [0], "265148984": [0], "265728020": [0], "265728723": [0], "265952126": [0], "266053898": [0], "266241572": [0], "266467153": [0], "266750793": [0], "267821291": [0], "267892650": [0], "268488368": [0], "268711431": [0], "268945661": [0], "269620004": [0], "269680663": [0, 1], "269877544": [0, 1], "270311702": [0], "270340479": [0], "270551024": [0], "271207385": [0], "271435583": [0], "272057442": [0], "272554750": [0], "272984235": [0], "273175159": [0], "273361470": [0], "274092279": [0], "274265028": [0], "274377972": [0], "275403121": [0], "275459085": [0, 1], "275464458": [0], "275703498": [0, 1], "275840476": [0, 1], "276059938": [0], "276090731": [0], "276148559": [0, 1], "276463771": [0], "277078698": [0], "277138829": [0], "277374024": [0], "277456387": [0], "277849847": [0], "277902632": [0], "277990936": [0], "277993739": [0], "278054598": [0], "278127227": [0], "278458093": [0], "278497444": [0], "279048517": [0, 1], "279137674": [0], "279265117": [0], "279304791": [0], "279569321": [0], "279829374": [0], "279900278": [0], "279967540": [0], "280265929": [0], "280320626": [0], "280884520": [0], "281199059": [0], "281421965": [0], "281553650": [0], "281838630": [0], "281867022": [0], "281958309": [0, 1], "282115685": [0], "282146627": [0], "282170511": [0], "282515001": [0], "282543050": [0], "282759091": [0], "282944020": [0], "283346416": [0], "283973955": [0], "284108542": [0], "284249509": [0, 1], "285536944": [0], "285651275": [0], "285761502": [0], "285838245": [0], "285998497": [0], "286115610": [0], "286126641": [0], "286827371": [0], "287100261": [0], "287101334": [0], "287331555": [0], "287369081": [0, 1], "287998653": [0], "288286689": [0], "288646202": [0], "288751724": [0], "288764558": [0], "288781345": [0], "289464761": [0], "289564463": [0], "289831924": [0], "290005260": [0], "290123471": [0], "290300940": [0], "290519967": [0], "290826298": [0], "290864560": [0], "291193801": [0], "291267577": [0], "291434006": [0], "291484158": [0], "291764062": [0], "291783909": [0, 1], "292147030": [0], "292170252": [0], "292505173": [0], "292519298": [0], "292583066": [0], "292651633": [0], "292805095": [0], "292973765": [0], "293582051": [0], "293932462": [0], "293990191": [0], "294415392": [0], "294467128": [0], "295243274": [0], "295598361": [0], "295753288": [0], "295769282": [0], "296135401": [0], "296178031": [0], "296647522": [0], "296712482": [0], "297069354": [0], "297167588": [0], "297223225": [0], "297266278": [0], "297574469": [0], "297832633": [0], "298126434": [0], "298203807": [0], "298845620": [0], "299235649": [0], "299533186": [0], "299773066": [0], "300223089": [0], "300236917": [0], "300453987": [0], "300846896": [0], "301414233": [0, 1], "301521739": [0], "301620089": [0], "301714741": [0], "301860710": [0], "302544856": [0], "302692137": [0], "302854923": [0], "303417319": [0], "304038547": [0], "304347546": [0], "304594319": [0], "304692104": [0], "304886591": [0], "304888363": [0], "305073077": [0], "305587977": [0], "305685841": [0], "305977209": [0], "306017852": [0], "306574743": [0], "306899405": [0], "307152869": [0], "307410795": [0], "307452553": [0], "307575654": [0], "307698304": [0], "307846988": [0], "308145880": [0], "308272905": [0], "308436561": [0], "308654696": [0, 1], "308877867": [0], "309201298": [0], "309229686": [0], "309388219": [0], "309449902": [0], "309700940": [0], "309800942": [0], "310058350": [0], "310071925": [0], "310115163": [0], "310122610": [0], "310944311": [0], "311003762": [0, 1], "311149539": [0], "311325605": [0], "311393594": [0], "311441206": [0], "312099727": [0], "312124250": [0], "312137270": [0], "312414128": [0], "312470508": [0], "312567151": [0, 1], "313056524": [0], "313390054": [0], "313468657": [0], "313550196": [0], "313614110": [0, 1], "313620512": [0, 1], "314335815": [0], "314363657": [0], "314375386": [0], "314474855": [0], "315341354": [0], "315365517": [0], "315441006": [0], "315479415": [0], "315488965": [0], "315597289": [0], "315864500": [0], "316388655": [0], "316700501": [0], "317000842": [0], "317104158": [0], "317207492": [0], "317389483": [0], "317424779": [0, 1], "317451981": [0], "317484694": [0], "317485701": [0], "317821208": [0], "317899639": [0], "318084353": [0], "318157945": [0], "318312068": [0], "318413902": [0], "318690683": [0], "318804621": [0], "319041952": [0], "319095298": [0], "319368329": [0], "319919381": [0], "320154353": [0], "320163505": [0], "320502510": [0], "320789740": [0], "320918619": [0], "321200569": [0], "321413197": [0], "321735416": [0], "321876307": [0], "322069632": [0], "322835064": [0, 1], "322852095": [0, 1], "323168257": [0], "323190024": [0], "323269268": [0], "323338128": [0], "323735982": [0], "323845285": [0], "323889856": [0], "324039663": [0], "325028434": [0], "325350573": [0], "325583995": [0], "325608475": [0], "325625585": [0], "325721667": [0], "325933372": [0], "325961111": [0], "326070656": [0], "326294930": [0], "326423053": [0], "326715676": [0], "327246597": [0], "327477526": [0], "327587091": [0], "327638247": [0], "327831303": [0], "327951146": [0], "328267457": [0], "328631761": [0], "328695654": [0], "329373516": [0], "329568049": [0], "329791286": [0], "329866247": [0], "330345570": [0], "330483612": [0], "330498879": [0], "330806060": [0], "330867357": [0], "331163674": [0], "331629849": [0], "331751807": [0], "331948066": [0], "331989378": [0], "332026975": [0], "332109662": [0], "332197125": [0], "332447621": [0], "332494513": [0], "332575810": [0], "332598599": [0], "333030595": [0], "333175856": [0], "333865347": [0], "333923789": [0], "334186249": [0], "334664245": [0], "334749370": [0], "334776545": [0], "334965583": [0], "335053898": [0], "335160636": [0], "335404231": [0], "335518588": [0], "335916767": [0], "336569514": [0], "336922830": [0], "336975594": [0, 1], "337052318": [0], "337108309": [0], "337297381": [0], "337820423": [0], "337914843": [0], "338223634": [0], "338429343": [0, 1], "338636146": [0], "338700786": [0], "338863896": [0], "338889043": [0], "339042992": [0], "339077713": [0], "339106766": [0], "339212669": [0], "339752732": [0], "339846111": [0], "340253370": [0], "340361724": [0], "340486200": [0], "340513317": [0], "340531638": [0], "340712568": [0], "340715864": [0, 1], "340862221": [0], "340904864": [0, 1], "341487438": [0], "341538822": [0], "341812625": [0], "342354879": [0], "342812452": [0], "342839586": [0], "343763147": [0], "344266653": [0], "344348527": [0], "344426768": [0], "345019564": [0], "345119630": [0, 1], "345290112": [0], "345482856": [0], "345486049": [0], "345807172": [0], "346860479": [0], "347009964": [0], "347550254": [0], "347795268": [0], "348006369": [0], "348078268": [0], "348114879": [0], "348170702": [0, 1], "348308198": [0], "348365486": [0], "348444563": [0], "348489157": [0], "348710961": [0], "348880108": [0], "349094507": [0], "349250456": [0], "349267111": [0], "349380938": [0], "349621373": [0], "349657843": [0], "349734496": [0], "350127999": [0], "350166488": [0], "350651412": [0], "350909348": [0], "350928653": [0], "350992061": [0], "351196122": [0], "351293188": [0], "351318022": [0], "352394355": [0], "352553154": [0], "352938566": [0], "353002372": [0], "353058434": [0], "354051396": [0], "354325572": [0, 1], "354352866": [0], "354717804": [0], "355230055": [0], "355637114": [0], "355676182": [0], "355701494": [0], "355819631": [0], "355957898": [0], "356113051": [0, 1], "356297794": [0], "356748989": [0], "356885738": [0], "356897479": [0], "356970904": [0], "356972027": [0], "357134422": [0], "357222384": [0], "357653306": [0], "358370301": [0], "358385343": [0, 1], "358448355": [0], "358987667": [0], "359301856": [0], "359313960": [0], "359651274": [0], "359725933": [0], "360005665": [0], "360095257": [0], "360096552": [0, 1], "360498079": [0], "360541263": [0], "360711687": [0], "360900508": [0], "361439705": [0], "363064351": [0], "363197472": [0], "363198793": [0], "363208007": [0], "363218485": [0], "363337492": [0], "363428747": [0], "363444733": [0], "363901385": [0], "363955834": [0], "364244101": [0], "364663295": [0], "365073832": [0], "365178494": [0], "365298471": [0], "365412256": [0], "365837802": [0], "366126368": [0], "366171189": [0], "366929551": [0], "367009121": [0], "367101844": [0], "367433440": [0], "367449903": [0], "367612754": [0], "367735129": [0], "367763780": [0], "368259700": [0], "368892291": [0], "369025575": [0], "369207128": [0], "369353139": [0], "369513350": [0], "369623081": [0], "369652425": [0], "370243736": [0], "370318941": [0], "371048578": [0], "372221498": [0], "372575530": [0], "372773615": [0], "372781703": [0], "372801848": [0], "373501617": [0], "373519674": [0], "373622459": [0], "374083000": [0], "374221274": [0], "374603764": [0], "374651354": [0], "375170912": [0], "375437008": [0], "375797696": [0], "376599038": [0], "376611176": [0], "376637070": [0, 1], "376711579": [0], "376917274": [0], "377182053": [0], "377211144": [0], "377682745": [0], "377896308": [0], "378172033": [0], "378471475": [0], "378478081": [0], "378496667": [0], "378682324": [0], "378959331": [0], "379412814": [0], "379451364": [0], "379548254": [0], "379555038": [0], "379577379": [0], "380315429": [0], "380355230": [0], "380648295": [0, 1], "381291555": [0], "381607264": [0], "382167851": [0], "382215146": [0], "382582045": [0], "382768342": [0], "383283663": [0], "383304380": [0], "383446765": [0], "383491154": [0], "383807155": [0], "383925278": [0], "384137170": [0], "384254877": [0], "384555822": [0], "384574370": [0], "384633156": [0], "384945688": [0], "385382973": [0], "385693566": [0], "386067394": [0], "386799405": [0], "386955080": [0], "387413389": [0], "387604100": [0], "387896524": [0], "388721479": [0, 1], "388991661": [0], "389373580": [0], "389804057": [0], "389851760": [0], "390051431": [0], "390196269": [0], "390228038": [0], "390893024": [0], "391163285": [0], "391413980": [0], "391795982": [0], "392007652": [0], "392234088": [0, 1], "392507973": [0], "393248793": [0], "393376178": [0], "394345284": [0], "394359718": [0], "394438449": [0], "394676667": [0], "395098866": [0], "395474608": [0], "395745507": [0], "395777043": [0, 1], "395860684": [0], "395921188": [0], "395951875": [0], "396750981": [0], "396780470": [0], "396980518": [0], "397371069": [0, 1], "397556908": [0], "397719315": [0], "397766794": [0], "398043610": [0], "398105261": [0], "399313569": [0], "399590958": [0], "399729976": [0], "400259049": [0], "400269723": [0], "400324592": [0], "400479446": [0], "400773052": [0], "400895910": [0], "401453675": [0], "401611017": [0], "402002541": [0], "402040304": [0], "402041278": [0], "402804247": [0], "402879783": [0], "402908883": [0], "403224798": [0], "403494393": [0], "403628304": [0], "403812454": [0], "403965765": [0], "403980441": [0], "403992983": [0], "404098196": [0, 1], "404277572": [0], "404584064": [0], "404728657": [0], "404778772": [0, 1], "404801139": [0], "405035131": [0], "405181541": [0], "405244981": [0], "406675262": [0], "406684747": [0], "406823874": [0], "406971103": [0], "407000942": [0], "407101308": [0], "407116828": [0], "407239220": [0], "407427669": [0], "407440667": [0], "407502504": [0, 1], "407525662": [0], "407614240": [0], "407746476": [0], "407957030": [0], "408028328": [0], "408718039": [0], "409109375": [0], "409574927": [0], "410148565": [0], "410433966": [0], "410513936": [0], "410525916": [0], "410571672": [0], "410627894": [0], "410756837": [0], "411221162": [0], "411497591": [0], "411756198": [0], "411928314": [0], "411978136": [0], "412211207": [0], "412301107": [0], "412366130": [0], "412396687": [0, 1], "412444884": [0], "412511990": [0], "412704463": [0], "412772734": [0], "412935571": [0], "413211883": [0], "413248318": [0], "413315338": [0], "413432131": [0], "413879128": [0, 1], "413918571": [0], "413985250": [0], "414074069": [0, 1], "414527314": [0, 1], "414985277": [0], "415552153": [0], "416041576": [0], "416295447": [0], "416374900": [0], "416435180": [0], "416884573": [0], "417050397": [0], "417092091": [0], "417224255": [0], "417430854": [0], "418393985": [0], "418446222": [0], "418647649": [0], "418721075": [0], "418852647": [0], "418908025": [0], "419304960": [0], "419354685": [0], "419584779": [0], "419782680": [0, 1], "419796522": [0], "420015314": [0], "420191219": [0], "421039219": [0], "421190756": [0], "421215245": [0], "421356234": [0], "421364330": [0], "421415615": [0], "421871015": [0], "422016323": [0], "422297415": [0], "422970497": [0], "423034394": [0, 1], "424663156": [0], "424680247": [0, 1], "424734539": [0, 1], "425171468": [0], "425640900": [0], "425708755": [0, 1], "425874228": [0], "426031570": [0], "426109703": [0], "426180928": [0], "426936614": [0], "426941929": [0], "426994449": [0], "427115980": [0], "427307396": [0], "427546391": [0], "428034107": [0], "428142884": [0], "428573349": [0], "428841595": [0], "428938561": [0], "429459411": [0], "430481132": [0, 1], "430491980": [0], "430929142": [0], "431082368": [0], "431404747": [0], "431498087": [0, 1], "431513199": [0], "431542134": [0], "431845496": [0], "432524531": [0], "433019917": [0], "433436814": [0], "433722666": [0], "433843616": [0], "433896588": [0], "433972566": [0], "434129719": [0, 1], "434160554": [0], "434206904": [0], "434431386": [0], "434797431": [0, 1], "435044696": [0], "435286158": [0], "435306832": [0], "435756806": [0], "435875495": [0], "435919066": [0], "436192555": [0], "436334712": [0], "436818171": [0], "437616367": [0], "437676662": [0], "438055027": [0], "438184709": [0], "438185674": [0], "438289172": [0], "438904484": [0], "439190784": [0], "439212967": [0], "439338362": [0], "440557875": [0], "441487566": [0], "441613140": [0], "441830701": [0], "442027998": [0], "442134418": [0], "442735988": [0, 1], "442832912": [0], "443062111": [0], "443074962": [0, 1], "443702612": [0], "443892439": [0], "443963053": [0], "444068067": [0], "444602151": [0], "444815883": [0], "444881429": [0, 1], "444997483": [0], "445090399": [0], "445202076": [0], "445447726": [0], "445602382": [0], "446144293": [0], "446483208": [0], "446556112": [0], "446798566": [0], "446935861": [0], "446966235": [0], "447309709": [0], "447351472": [0], "447410813": [0], "447569451": [0], "448063449": [0], "448074465": [0], "448076148": [0], "449009608": [0], "449084975": [0], "449260967": [0, 1], "449305917": [0], "449370414": [0], "450852331": [0], "451556617": [0], "451573129": [0], "451982056": [0], "452075420": [0], "452092599": [0], "452216079": [0], "452298088": [0], "452368005": [0], "452503240": [0, 1], "452816957": [0], "452846954": [0], "453017552": [0], "453117939": [0], "453131432": [0], "453213111": [0], "453414738": [0, 1], "453429008": [0], "453729597": [0], "454075511": [0], "454543569": [0], "454985085": [0], "455266987": [0], "455356290": [0], "455444277": [0], "455543211": [0], "455687003": [0], "455738966": [0], "455762729": [0], "455925678": [0], "456753772": [0], "456755023": [0], "456989192": [0], "457038433": [0], "457467527": [0], "457497507": [0], "457646334": [0], "457840820": [0], "457877425": [0], "457942861": [0], "458245964": [0], "458339564": [0], "458804210": [0], "459218190": [0], "459935911": [0], "460075409": [0], "460146513": [0], "460223942": [0], "460601563": [0], "460678875": [0], "461003635": [0], "461125375": [0], "461214357": [0], "461330018": [0], "461597927": [0], "461726118": [0], "461868796": [0], "462003109": [0], "462162181": [0], "462573240": [0], "462826762": [0], "463010326": [0], "463276342": [0], "463465182": [0], "463544054": [0], "463636130": [0], "464088390": [0], "464459713": [0], "464579730": [0], "464814466": [0], "465047791": [0], "465165131": [0], "465254226": [0], "465457282": [0, 1], "465535910": [0], "465731294": [0], "465914505": [0], "466051741": [0], "466331612": [0], "466425795": [0], "466762018": [0], "466905523": [0, 1], "467020736": [0, 1], "467317796": [0], "467358798": [0], "467387796": [0], "467717241": [0], "467978003": [0], "468909542": [0], "468928438": [0], "469357284": [0], "469402236": [0], "469610657": [0], "469671730": [0], "469748531": [0], "470407468": [0], "470451737": [0], "470497474": [0], "470557716": [0, 1], "471063251": [0, 1], "471435509": [0], "471687605": [0], "471943665": [0], "472141616": [0], "472915678": [0], "472994805": [0], "473406351": [0], "474183922": [0], "474341235": [0], "474600492": [0], "474662618": [0], "474882239": [0], "476202548": [0], "476604846": [0], "476686255": [0], "476718071": [0], "477047342": [0], "477142533": [0], "477271360": [0], "477385230": [0], "477405830": [0], "477617375": [0], "477642108": [0], "477915367": [0], "477920498": [0], "478072268": [0], "478082589": [0], "478274381": [0], "478373739": [0], "478589417": [0], "478863214": [0], "478909222": [0], "479019192": [0], "479611566": [0], "479792215": [0], "480321730": [0], "480531608": [0], "480834630": [0], "480965751": [0], "481066538": [0], "481329249": [0], "481673243": [0], "481996348": [0], "482089050": [0], "482253467": [0], "482435610": [0], "482897418": [0], "482916242": [0], "482960943": [0], "483085745": [0], "483180179": [0], "483305294": [0], "483468595": [0], "483522083": [0], "483940448": [0], "484084827": [0], "484102532": [0], "484196209": [0], "484321672": [0], "484566478": [0], "484841521": [0], "485024213": [0], "485226940": [0], "485236744": [0], "485259865": [0], "485337979": [0], "485358252": [0], "486091284": [0], "486129756": [0], "486205768": [0], "486810741": [0], "487529240": [0], "488560839": [0], "489001964": [0], "489408945": [0, 1], "489862863": [0], "490216362": [0], "490392129": [0], "491124662": [0], "491556548": [0], "491562148": [0], "491708032": [0], "491878180": [0], "491954212": [0], "492284707": [0], "492896373": [0], "492973033": [0], "493090624": [0], "493338184": [0, 1], "493797492": [0], "493931041": [0], "494207180": [0], "494554710": [0], "495267940": [0], "495364592": [0], "495702296": [0], "495754379": [0], "495934940": [0], "496090324": [0], "496507253": [0], "496876676": [0], "497288631": [0], "497290236": [0], "497466073": [0, 1], "497473812": [0], "497482392": [0], "497771844": [0], "498030623": [0], "498325530": [0], "498460971": [0], "498491555": [0], "498903501": [0], "499341998": [0], "499656695": [0], "499704704": [0], "499875544": [0], "500069730": [0], "500261721": [0], "500296008": [0], "500439701": [0], "500467765": [0], "500532499": [0], "500564960": [0], "500603225": [0], "500721643": [0], "500747788": [0], "500787120": [0], "500975380": [0], "501019109": [0], "501281523": [0], "501304816": [0], "501341015": [0], "501428255": [0], "501618760": [0], "501655719": [0], "501916916": [0], "502146765": [0], "502172165": [0], "502319459": [0], "502630234": [0, 1], "502868300": [0], "503110428": [0], "503736874": [0], "504320834": [0], "504710279": [0], "504723216": [0], "505724785": [0], "506148896": [0], "506220406": [0], "506532946": [0], "507024215": [0, 1], "507026058": [0], "507052217": [0], "507083804": [0], "507290856": [0], "507715925": [0], "508009315": [0], "508009385": [0], "508288960": [0], "508532181": [0], "508787126": [0], "508940825": [0], "509004228": [0], "509006891": [0], "509297101": [0], "509371307": [0], "509503579": [0, 1], "509678263": [0], "509808983": [0, 1], "509911612": [0], "510063276": [0], "510099618": [0], "510117325": [0], "510145013": [0], "510202498": [0], "510457133": [0], "510935902": [0], "511050951": [0], "511208129": [0], "511257924": [0], "511633549": [0, 1], "511671532": [0], "511706672": [0], "511952984": [0], "512611716": [0], "512801513": [0], "513316688": [0], "513490716": [0], "513652868": [0], "513715614": [0], "513788519": [0], "513848459": [0, 1], "514601417": [0], "514908234": [0], "514920325": [0], "514970323": [0, 1], "515700357": [0], "515916599": [0], "515943011": [0], "516272236": [0], "516395046": [0], "516449509": [0], "516491879": [0], "516550357": [0], "516563876": [0], "516637911": [0], "516720209": [0], "516799900": [0], "516905097": [0], "517077422": [0], "517255653": [0, 1], "517947063": [0], "518022559": [0], "518491474": [0], "518639455": [0], "518720825": [0], "518787087": [0], "518798274": [0], "518984104": [0], "518990037": [0], "518995906": [0], "519101064": [0], "519269582": [0, 1], "519614210": [0], "519991823": [0, 1], "520050089": [0], "520096602": [0], "520104654": [0], "520299289": [0], "520668819": [0], "520967742": [0], "520974095": [0], "521057508": [0], "521223581": [0], "521324542": [0], "521409358": [0], "521882769": [0], "522230981": [0], "522539285": [0], "523117340": [0], "523131958": [0], "523164560": [0], "523870547": [0], "523993893": [0], "524241740": [0], "524542289": [0], "524684238": [0], "524741622": [0], "524950095": [0], "524977781": [0], "525376825": [0], "525542968": [0], "525968881": [0], "526520677": [0], "526592081": [0], "526673463": [0], "526718475": [0], "526760274": [0], "526894460": [0], "527177451": [0, 1], "527261880": [0], "527683112": [0], "527807219": [0], "527940748": [0], "528015816": [0], "528752618": [0], "528942033": [0], "529853785": [0], "529889600": [0], "530079983": [0], "530092955": [0], "530266001": [0, 1], "530481328": [0], "530550990": [0], "530559266": [0], "530562367": [0], "530603337": [0], "530864159": [0], "530894170": [0], "531199886": [0, 1], "531327438": [0], "531390943": [0], "531410454": [0], "532320999": [0], "532358950": [0], "532550227": [0], "532644218": [0], "532794386": [0], "533058006": [0], "533079405": [0], "533087006": [0], "533202275": [0], "533354110": [0], "533472554": [0], "533629309": [0], "533805302": [0, 1], "533957710": [0], "534007452": [0], "534137179": [0], "534233949": [0], "534776352": [0], "535049932": [0], "535636058": [0], "535696132": [0], "535874533": [0], "536061802": [0], "536677512": [0], "536830644": [0], "537115664": [0], "537146382": [0], "537215341": [0], "537226200": [0], "537644802": [0], "537979528": [0], "538103527": [0], "538267734": [0], "538816557": [0, 1], "539070087": [0], "539139357": [0], "539204224": [0], "539455234": [0], "539554609": [0], "539688342": [0], "539702460": [0], "539721644": [0], "540015838": [0], "540320893": [0], "540343300": [0], "540596978": [0], "540658797": [0], "540988812": [0], "541736504": [0], "541902633": [0], "543057229": [0], "543252644": [0], "543302771": [0], "543511156": [0], "545034135": [0], "545291794": [0], "545422534": [0], "545457662": [0], "545616471": [0], "545838102": [0], "545880814": [0], "546001473": [0], "546055181": [0], "546398320": [0], "546503802": [0], "546532662": [0], "546649401": [0, 1], "547158235": [0], "547184755": [0], "547458267": [0], "547644979": [0], "548439896": [0], "548777204": [0], "548894675": [0], "549143440": [0], "549586515": [0], "549633101": [0], "549809186": [0], "549853492": [0], "549862969": [0, 1], "549974398": [0], "550151369": [0], "550196619": [0], "550255155": [0], "550325330": [0], "550528292": [0], "550703999": [0], "550780640": [0], "551176071": [0], "551337561": [0], "551951784": [0], "552065958": [0], "552069318": [0, 1], "552069454": [0], "552191077": [0], "552226420": [0], "552365240": [0], "552376178": [0], "552463520": [0], "552822856": [0], "552837557": [0, 1], "552937996": [0], "552995187": [0], "553450909": [0], "553698762": [0], "553803417": [0], "554566359": [0], "554877259": [0, 1], "554883372": [0], "555341408": [0], "555885575": [0, 1], "556156368": [0], "556490650": [0], "556833571": [0], "557123654": [0], "557368878": [0], "557462157": [0], "557587740": [0], "558149359": [0, 1], "558233867": [0], "558620918": [0], "559182034": [0, 1], "559195890": [0], "559236028": [0], "559523376": [0], "559878977": [0], "559964116": [0], "560421177": [0], "560564854": [0], "560610446": [0], "560755477": [0], "560830036": [0], "560869501": [0], "561547445": [0, 1], "561871259": [0], "562953584": [0], "562957618": [0], "563123708": [0], "563513229": [0], "563561983": [0], "564121131": [0], "564243551": [0], "564249564": [0], "564797613": [0], "564903589": [0], "565264166": [0], "565342144": [0], "565397720": [0], "565500396": [0], "565749092": [0], "565854201": [0], "565956575": [0], "566259252": [0], "566335955": [0, 1], "566963387": [0], "567016803": [0], "567130645": [0], "567195239": [0], "567324867": [0], "567437149": [0], "567541104": [0], "567654294": [0], "568084904": [0], "568152321": [0], "568267762": [0], "568498071": [0], "568628088": [0], "568755622": [0], "569046167": [0], "569861059": [0], "570739883": [0], "571345741": [0], "571618207": [0], "571774041": [0, 1], "572685242": [0], "572737181": [0], "573420610": [0], "573828075": [0], "574400692": [0], "574679124": [0], "574711224": [0], "574810194": [0], "574984670": [0], "575223573": [0], "575546438": [0], "575651191": [0], "575692086": [0], "576239834": [0], "576388815": [0], "576698908": [0], "577174278": [0], "577454696": [0], "577619406": [0], "577745390": [0], "578298324": [0], "578427489": [0], "579240465": [0], "579353051": [0], "579428384": [0], "579955629": [0], "580600783": [0], "580695763": [0], "580716922": [0], "581108993": [0], "581298772": [0], "581433269": [0], "581608588": [0], "581715403": [0], "581728357": [0], "581781609": [0, 1], "581783554": [0], "581940082": [0], "582135118": [0, 1], "582293467": [0, 1], "582379240": [0], "582517482": [0], "582752589": [0], "583169134": [0], "583201092": [0], "583630734": [0], "584338221": [0], "584347340": [0], "584921957": [0, 1], "584963596": [0], "585117419": [0], "585170657": [0], "585249487": [0], "585572106": [0], "585611555": [0], "585692093": [0], "585737406": [0], "585806031": [0], "585819125": [0], "586082648": [0], "586349954": [0], "586769593": [0], "586877886": [0], "586966082": [0], "587008502": [0], "587061654": [0], "587292497": [0], "587438988": [0], "588306902": [0], "588565626": [0], "588895755": [0], "589060642": [0], "589371465": [0], "589479471": [0], "589695771": [0], "589883766": [0], "590171089": [0], "590311471": [0], "590409485": [0], "590719097": [0], "591012169": [0], "591032679": [0], "591058432": [0], "591079861": [0], "591216489": [0], "591773866": [0], "591773869": [0, 1], "592507817": [0, 1], "592517925": [0], "592739169": [0], "593351289": [0], "594055121": [0], "594201507": [0], "594886185": [0], "594946082": [0], "594952755": [0], "595224378": [0, 1], "595337039": [0], "595361176": [0], "595379234": [0], "595379938": [0], "595696529": [0], "595833006": [0, 1], "595849988": [0], "595923845": [0], "596162707": [0], "596235578": [0], "596838607": [0], "597049538": [0], "597134377": [0], "597170971": [0], "597261771": [0], "597496569": [0], "597669156": [0], "598263117": [0], "598306806": [0], "598790758": [0], "598864644": [0], "598865941": [0], "598977988": [0], "599216574": [0], "599268927": [0], "599495519": [0, 1], "600018940": [0], "600198136": [0], "601319808": [0], "601517747": [0], "601520917": [0], "601715528": [0], "601747659": [0], "601799903": [0], "601976343": [0], "601977847": [0], "602026376": [0], "602233414": [0], "602233543": [0], "602421134": [0], "603010933": [0], "603096725": [0], "603896926": [0], "603898311": [0, 1], "604100702": [0], "604149578": [0], "604178419": [0], "604231793": [0], "604252237": [0], "604463352": [0], "604464725": [0], "604502868": [0], "604566385": [0], "604611167": [0], "605050327": [0], "605551831": [0], "605659358": [0], "605910100": [0], "606174700": [0], "606434878": [0], "606455481": [0], "606764380": [0], "606837036": [0], "607019970": [0, 1], "607244681": [0], "607305870": [0], "607563688": [0, 1], "607835146": [0], "608317735": [0], "609381562": [0], "609543837": [0], "609609351": [0], "609682927": [0], "610312004": [0, 1], "610741121": [0], "610814575": [0, 1], "610921096": [0], "611313675": [0], "611643373": [0], "611644110": [0], "612594384": [0], "612686837": [0], "613202869": [0], "613379900": [0, 1], "613571247": [0], "613729570": [0], "613841341": [0, 1], "615149878": [0], "615185875": [0], "615200654": [0], "615221315": [0], "615279283": [0], "615682097": [0], "615936690": [0], "615983204": [0], "616134165": [0], "616279983": [0], "616407757": [0], "616470217": [0], "616537408": [0], "616680913": [0], "617018165": [0], "617041815": [0], "617072752": [0], "617104948": [0], "617272657": [0], "617472088": [0], "617506812": [0], "617691316": [0], "617758035": [0], "618026391": [0], "618027279": [0], "618321096": [0], "618912554": [0], "618938949": [0], "619328200": [0], "619455725": [0], "619488393": [0], "620317935": [0], "620455651": [0], "620626333": [0], "620796006": [0], "621113547": [0], "621608334": [0], "621943314": [0], "622339711": [0], "622371230": [0], "622420001": [0], "623112144": [0], "623255339": [0, 1], "623957403": [0], "623964265": [0], "623975107": [0], "624055769": [0], "624115386": [0], "624579608": [0], "624841433": [0], "625172847": [0, 1], "625266975": [0], "625284588": [0], "625331333": [0], "625510911": [0], "625683352": [0], "625967633": [0], "626387254": [0], "626702243": [0], "627035745": [0], "627120993": [0], "627234143": [0], "627274843": [0], "628030887": [0], "629016640": [0], "629661830": [0], "629820702": [0], "629983379": [0], "630173388": [0], "630294851": [0], "630534767": [0], "631286233": [0], "631305991": [0, 1], "631356534": [0], "631460397": [0], "631520446": [0], "632336952": [0], "632390506": [0], "632678843": [0], "633147893": [0], "633289114": [0], "633824263": [0], "634428272": [0], "634473022": [0], "634658012": [0], "634900206": [0], "634953619": [0], "635133751": [0], "635501895": [0], "635875049": [0], "636223568": [0, 1], "636286203": [0], "636538595": [0], "636890281": [0], "637055945": [0], "638034707": [0], "638086746": [0], "638488704": [0], "638740410": [0], "638753816": [0], "638818590": [0, 1], "638825066": [0], "638897149": [0], "639152988": [0], "639633438": [0], "639918156": [0], "640041093": [0], "640048600": [0], "640310618": [0], "640321468": [0], "640322038": [0], "640462058": [0], "640729350": [0], "640766143": [0], "641161257": [0], "641350988": [0], "641532977": [0], "641558916": [0], "641565586": [0], "642181990": [0], "642410400": [0], "642419301": [0], "642648415": [0], "642705472": [0], "642785603": [0], "642948641": [0], "643088161": [0], "643187688": [0], "643395458": [0], "643506624": [0], "643791274": [0], "644172440": [0], "644422174": [0], "644543681": [0], "644608044": [0], "644729636": [0], "645238356": [0], "645511122": [0], "645695827": [0], "645789062": [0], "645855060": [0, 1], "646108004": [0], "647055772": [0], "647110667": [0], "647256141": [0], "647273780": [0], "647466341": [0], "647544307": [0], "647598241": [0], "647631879": [0], "647828576": [0], "647895578": [0], "648026631": [0], "648213861": [0], "648424704": [0], "648518397": [0], "648548715": [0], "649240497": [0], "649254437": [0], "649289734": [0, 1], "649445508": [0], "649562635": [0], "649572003": [0], "649615940": [0], "650050366": [0], "650071993": [0], "650149633": [0], "650494330": [0], "650856344": [0], "650930096": [0], "651056057": [0], "651077989": [0], "651382561": [0], "651714622": [0], "651716639": [0], "651828358": [0], "651960571": [0], "652331505": [0], "652423632": [0], "652812049": [0], "652985332": [0], "652987552": [0], "653137327": [0], "653199365": [0], "653202700": [0], "653456467": [0], "654340247": [0, 1], "654770982": [0], "654798325": [0], "654900338": [0], "654921404": [0], "655551179": [0], "655578281": [0], "655612956": [0], "655647909": [0], "655705493": [0, 1], "655738067": [0], "656092547": [0], "656353735": [0], "656716979": [0], "657093136": [0], "657411682": [0], "657429296": [0], "658354017": [0], "658517609": [0], "658619469": [0], "658692633": [0], "658974620": [0], "659360227": [0], "659453616": [0], "659461669": [0], "659470552": [0], "659546416": [0, 1], "659999073": [0], "660067209": [0], "661107788": [0], "661332960": [0], "661436337": [0], "661739834": [0], "661815937": [0, 1], "662417068": [0], "662467381": [0], "662491858": [0], "662719848": [0], "663151947": [0], "663179487": [0], "663220457": [0], "663606076": [0], "663824561": [0], "663984082": [0], "664185048": [0], "664226787": [0], "664282351": [0], "664467177": [0], "664477099": [0], "664562169": [0], "664616410": [0], "664700721": [0], "664704162": [0, 1], "664972058": [0, 1], "665370435": [0], "666068098": [0], "666152819": [0], "666295297": [0], "666399550": [0], "666428932": [0], "667676819": [0], "667731514": [0], "667934645": [0], "667966635": [0], "668058446": [0], "668300610": [0], "668301375": [0], "668500461": [0], "668636452": [0], "668768330": [0], "669165215": [0], "669267540": [0], "669605879": [0], "670057638": [0], "670271323": [0], "670440940": [0], "670548986": [0], "670959663": [0], "670995774": [0], "671112661": [0], "671227995": [0], "671255893": [0], "671293820": [0], "671732462": [0], "672056062": [0], "672312753": [0], "672338539": [0], "672396338": [0, 1], "672403649": [0, 1], "672944687": [0, 1], "673259336": [0], "673294552": [0], "673391951": [0], "673639245": [0], "673739778": [0], "673886039": [0], "673932128": [0], "673945036": [0], "674140260": [0], "674191182": [0], "674265932": [0, 1], "674454567": [0], "674534124": [0], "674868982": [0], "675102531": [0], "675690710": [0], "675703250": [0], "675900033": [0], "675911689": [0], "676029780": [0], "676322025": [0], "676473389": [0], "676803490": [0], "676925150": [0], "677035752": [0], "677054924": [0], "677336635": [0], "677631603": [0], "678178447": [0], "678627499": [0], "678864642": [0], "679140717": [0], "679564686": [0], "679574910": [0], "680524307": [0], "681075213": [0], "681262321": [0], "681275246": [0], "681617025": [0], "681922647": [0], "682021051": [0], "682335131": [0], "682341680": [0], "682610807": [0, 1], "683291352": [0], "683581871": [0, 1], "683720300": [0], "683921909": [0], "684091988": [0], "684652779": [0, 1], "684839873": [0], "685012154": [0], "685188414": [0], "685251127": [0], "685336448": [0], "685757942": [0], "685797913": [0], "685955506": [0], "686598147": [0], "686776195": [0], "686913340": [0], "687034150": [0], "687143725": [0], "687739194": [0], "687983898": [0], "688014483": [0], "688213643": [0], "688346604": [0], "688679261": [0, 1], "688701935": [0], "688726636": [0], "688905196": [0], "689173726": [0], "689213009": [0], "689252779": [0], "689257409": [0], "689859122": [0], "690089442": [0], "690198792": [0], "690307261": [0], "690672429": [0], "690685329": [0], "690690187": [0], "690766799": [0], "691046500": [0], "691150452": [0], "691403960": [0], "691479299": [0], "691654601": [0], "691906991": [0], "691931607": [0], "692006416": [0], "692684120": [0], "692924453": [0], "693009395": [0], "693864198": [0], "694194347": [0], "694277756": [0], "694804516": [0], "694832338": [0], "694960383": [0], "695308241": [0], "695345735": [0], "695381718": [0, 1], "695408043": [0], "695663086": [0], "695685136": [0], "696539805": [0], "696641381": [0], "696662473": [0, 1], "696745620": [0], "697050685": [0], "697090611": [0], "697255374": [0], "697570929": [0], "697660056": [0], "697887502": [0], "698016663": [0], "698156837": [0], "698608122": [0], "698728563": [0], "699258424": [0, 1], "699744134": [0], "700503612": [0], "700643111": [0], "700755172": [0], "700915293": [0], "700955543": [0], "701027273": [0], "701539568": [0], "701776934": [0], "702304598": [0], "702476203": [0], "702589198": [0], "702591703": [0], "702729856": [0], "702826885": [0], "703104449": [0], "703305948": [0], "703384662": [0], "703595598": [0], "703973136": [0, 1], "704039092": [0], "704401999": [0], "704637932": [0], "704984975": [0], "705009728": [0], "705081468": [0], "705096160": [0], "705458013": [0], "705496832": [0], "705549320": [0], "705840154": [0], "706089615": [0], "706106909": [0], "706350843": [0], "706457920": [0], "706719005": [0], "706992705": [0], "707198875": [0], "707398093": [0], "707524310": [0], "707654227": [0], "707708804": [0], "707940700": [0], "708097287": [0], "708212005": [0], "708699177": [0], "709122084": [0], "709171997": [0], "709490237": [0], "709963281": [0], "709988799": [0], "710360022": [0], "710706924": [0], "710883573": [0], "710946621": [0], "710951686": [0], "711033943": [0], "711070049": [0], "711155162": [0], "711260003": [0], "711345926": [0], "711477364": [0], "711569239": [0], "711670075": [0], "713196976": [0], "713216044": [0], "713477922": [0], "714169639": [0], "714445737": [0], "715038481": [0], "715107162": [0], "715186911": [0], "715267526": [0], "715354141": [0], "715475334": [0], "715536866": [0], "715616569": [0], "715642128": [0], "715992189": [0], "716322338": [0], "716347799": [0], "716456627": [0], "716658365": [0], "716785312": [0], "717024897": [0], "717327874": [0], "717511100": [0], "717729422": [0], "717807998": [0], "717812418": [0], "717931771": [0], "718215939": [0], "718329592": [0], "718854700": [0], "719417975": [0], "719507284": [0], "719782315": [0], "719818349": [0], "719888097": [0], "719920210": [0], "719953224": [0], "720030588": [0], "720608536": [0, 1], "720884823": [0], "720890655": [0], "721002383": [0], "721343875": [0], "721851651": [0], "721988698": [0], "722491144": [0], "722513642": [0], "722676370": [0, 1], "722764957": [0], "723284623": [0], "723474382": [0], "723823293": [0], "724038641": [0], "724260145": [0], "724443104": [0], "724468204": [0], "724539017": [0], "724650313": [0], "725104507": [0], "725124772": [0], "725308424": [0], "725483775": [0], "725499739": [0], "725544060": [0], "725993539": [0], "726225283": [0], "726532706": [0], "726772408": [0], "726823936": [0], "727088046": [0], "727521513": [0], "728213957": [0], "728273404": [0], "728461880": [0], "728578936": [0], "728743046": [0], "728774702": [0], "728843321": [0, 1], "728875898": [0], "729211346": [0], "729371564": [0], "729409187": [0], "729555341": [0], "730298039": [0], "730392655": [0], "730716858": [0], "730756631": [0], "730849655": [0], "730974292": [0], "731011091": [0], "731150422": [0, 1], "731563795": [0], "731812473": [0], "731952514": [0], "732244579": [0], "732502161": [0], "732503122": [0], "732516351": [0, 1], "732531921": [0, 1], "733021054": [0], "733112888": [0], "733437542": [0], "733692597": [0], "733753402": [0], "733769934": [0], "734109570": [0], "734314570": [0], "734326593": [0], "734507199": [0], "734682198": [0, 1], "734902131": [0], "734999262": [0], "735013522": [0], "735060648": [0], "735090270": [0], "735683251": [0], "735766313": [0], "735887007": [0], "735898949": [0, 1], "735930068": [0], "735937229": [0], "736057916": [0], "736062503": [0], "736497602": [0], "736611384": [0], "736689278": [0], "736841410": [0], "736961030": [0], "737025437": [0, 1], "738097996": [0], "738166755": [0], "738298704": [0, 1], "738393734": [0], "738465905": [0], "738672072": [0], "738995201": [0, 1], "739036223": [0], "739123603": [0], "739623755": [0], "739658661": [0], "739884501": [0], "739911884": [0], "740203839": [0], "740258477": [0], "740399148": [0], "740659079": [0, 1], "740964101": [0], "740973799": [0], "741657735": [0], "741881384": [0], "741948236": [0, 1], "742327223": [0], "742607986": [0, 1], "742685496": [0], "742785993": [0], "743169671": [0], "743230125": [0], "743517345": [0], "743790309": [0], "744252651": [0], "744498808": [0], "744682428": [0], "744855377": [0], "745078505": [0], "745514319": [0], "745580811": [0], "745725729": [0], "746121291": [0], "746175142": [0], "746656106": [0], "746674974": [0], "747745326": [0, 1], "748381554": [0], "750204135": [0], "750262473": [0], "751028897": [0], "751488689": [0], "751578276": [0], "751583748": [0], "752202499": [0], "753011549": [0], "753575366": [0], "753924726": [0], "754050338": [0, 1], "754052718": [0], "754236329": [0], "754328648": [0], "754556465": [0], "754710721": [0], "754785153": [0], "755109478": [0], "755420621": [0], "756271020": [0], "756499974": [0], "756531880": [0], "757300127": [0], "757340054": [0], "757464245": [0, 1], "757718170": [0], "757822799": [0], "757833507": [0], "757990926": [0, 1], "758340686": [0], "759362989": [0], "759438756": [0], "759783059": [0], "760293054": [0], "760439615": [0], "760445147": [0], "761059870": [0], "761274027": [0], "761799111": [0], "761882068": [0, 1], "761955985": [0], "762252019": [0], "762726492": [0], "762895753": [0], "763026401": [0], "763323851": [0], "763757754": [0, 1], "763892824": [0], "763957269": [0], "764026188": [0, 1], "764121003": [0], "764199087": [0, 1], "764422785": [0], "765125323": [0], "765161635": [0], "765338764": [0], "765355974": [0], "765727741": [0, 1], "766811628": [0], "767010894": [0], "767129686": [0], "767201985": [0], "767221391": [0], "767241724": [0], "767840219": [0], "768228605": [0], "768486896": [0], "768584705": [0], "769041166": [0, 1], "769107838": [0], "769157505": [0], "769167522": [0], "769227835": [0], "769239516": [0], "769273587": [0], "769656705": [0], "769781011": [0], "769925613": [0], "770104027": [0], "770347776": [0], "771094173": [0, 1], "771107536": [0], "771271520": [0], "771733034": [0], "771903312": [0], "772046780": [0], "772115343": [0], "772260196": [0], "772593547": [0], "772660324": [0], "773121991": [0], "773149693": [0], "773574168": [0], "773734082": [0], "773805271": [0], "774004098": [0], "774415308": [0], "774521485": [0], "774669401": [0], "775727608": [0], "776795544": [0], "777077054": [0, 1], "777130524": [0], "777243002": [0], "777368322": [0], "777421909": [0], "777791836": [0], "777833606": [0], "778003321": [0], "778084511": [0], "778707703": [0], "778882308": [0], "779321671": [0], "779592201": [0], "779596786": [0], "780457835": [0], "780465493": [0], "780780693": [0], "780889792": [0], "780924727": [0], "781121085": [0], "781244055": [0], "781580069": [0], "781625683": [0], "781726073": [0], "781850122": [0], "781891428": [0], "782449521": [0], "782805084": [0], "782921087": [0], "782946981": [0], "783492325": [0, 1], "783634649": [0], "783937834": [0], "784516801": [0], "784544483": [0], "784584198": [0], "784653812": [0], "784713916": [0], "784740720": [0], "784741627": [0], "785069220": [0], "785090915": [0], "785120255": [0], "785193353": [0, 1], "785237938": [0, 1], "785723566": [0], "785878104": [0], "785881411": [0], "785983503": [0], "786010838": [0], "786167676": [0], "786330215": [0], "786346955": [0], "786481012": [0], "786919501": [0], "787106636": [0], "787156400": [0], "787296866": [0, 1], "787373723": [0], "787450983": [0], "787814735": [0], "788039162": [0], "788894841": [0], "788912902": [0], "789587704": [0], "789767171": [0], "790064767": [0], "790175365": [0], "790217214": [0], "790293848": [0], "790380194": [0], "790381820": [0], "790451332": [0], "790480690": [0], "790646205": [0], "790746751": [0], "791631888": [0], "791693863": [0], "792035824": [0], "792690851": [0], "792928976": [0], "792986145": [0], "793635016": [0], "794701140": [0], "794994619": [0], "795313769": [0], "795896922": [0], "795998829": [0], "796329027": [0, 1], "796362906": [0, 1], "796426620": [0], "796686675": [0], "796733927": [0], "796984629": [0], "797413777": [0], "797621604": [0], "797810744": [0], "798227733": [0], "798279449": [0], "798397020": [0], "798618594": [0], "798934053": [0], "799123326": [0], "799304646": [0], "799748222": [0], "799759269": [0], "799821622": [0], "799998725": [0], "800248914": [0], "800254686": [0], "800329996": [0], "800451110": [0], "800558796": [0, 1], "800689175": [0], "801457739": [0], "802403082": [0, 1], "802662800": [0, 1], "802668615": [0], "802848159": [0], "802995571": [0], "803044173": [0], "803281301": [0], "803374643": [0], "803684533": [0], "803808188": [0], "803903343": [0], "803919303": [0], "803986259": [0], "804000812": [0], "804022371": [0], "804043090": [0], "804917686": [0], "804970562": [0], "805286849": [0], "805664476": [0], "805963909": [0], "806145785": [0], "806300803": [0], "806336415": [0], "806392229": [0], "806869825": [0], "807189896": [0], "807590644": [0], "807673157": [0], "807980153": [0], "808027259": [0], "808084666": [0], "808363009": [0], "808397287": [0], "808746029": [0], "809033396": [0], "809287450": [0], "809606814": [0], "810759117": [0], "811512461": [0, 1], "811602299": [0], "811823515": [0], "812131642": [0], "812440317": [0], "812902910": [0], "812905987": [0], "813057394": [0], "813901679": [0], "814524307": [0], "814948904": [0], "815064557": [0], "815196819": [0], "815807992": [0], "816018287": [0], "816743374": [0], "817124833": [0], "817629952": [0], "818105796": [0], "818485300": [0, 1], "819434121": [0], "819645829": [0], "820365425": [0], "820387001": [0], "820829824": [0], "820877962": [0], "821067096": [0], "821505796": [0], "821604496": [0], "821711139": [0], "821962786": [0], "822058945": [0], "822084160": [0], "822092668": [0], "822169435": [0], "823401051": [0], "823465823": [0], "823555147": [0], "823687375": [0], "824025231": [0], "824123549": [0], "824627425": [0], "825352780": [0], "825369591": [0], "825380935": [0], "825715181": [0], "825759173": [0], "825830818": [0], "826272930": [0], "826324989": [0], "826590306": [0, 1], "826866730": [0], "826895761": [0], "827083237": [0], "827086495": [0], "827158995": [0, 1], "827331440": [0, 1], "827348227": [0], "827947916": [0], "828395631": [0], "828529597": [0], "828884964": [0], "829028624": [0], "829118344": [0], "829623384": [0], "830196026": [0], "830345269": [0], "830686391": [0], "830776536": [0], "831303686": [0], "831363057": [0], "832033475": [0], "832134543": [0], "832273802": [0], "832694117": [0], "833071597": [0], "833516556": [0], "833572362": [0], "833850036": [0], "833983614": [0], "833987143": [0], "834036471": [0], "834247722": [0], "834374984": [0], "834494356": [0], "834540320": [0], "835273616": [0], "835380151": [0], "835489080": [0], "835561910": [0], "835844933": [0], "836051410": [0], "836587193": [0], "837055742": [0], "837072746": [0], "837638668": [0], "837647269": [0], "837824133": [0], "838065243": [0], "838158391": [0], "838244219": [0], "838578819": [0], "838834374": [0, 1], "838904518": [0], "838979037": [0], "839861457": [0], "840183806": [0], "840240303": [0], "841652378": [0], "841758616": [0], "842031089": [0], "842150366": [0], "842208644": [0], "842233722": [0], "842243049": [0], "842369930": [0], "842384738": [0], "842744397": [0], "842982591": [0], "843161565": [0], "843222357": [0], "843297332": [0], "843448824": [0], "843525362": [0], "843726299": [0], "844083699": [0], "844091374": [0], "844191549": [0], "844237898": [0], "844401158": [0], "844530832": [0], "844560568": [0], "844719536": [0], "844889548": [0], "844891850": [0], "845539255": [0, 1], "845620928": [0], "846607505": [0], "846688378": [0], "846886789": [0], "847377373": [0], "847387526": [0], "847413842": [0], "848073035": [0], "848085513": [0], "848134017": [0], "848157261": [0], "848174238": [0], "848242290": [0], "848259442": [0], "848735068": [0], "848753776": [0], "848849844": [0], "848954276": [0], "849291520": [0], "849393417": [0], "849458740": [0], "850466997": [0, 1], "850521314": [0], "850758917": [0], "851527353": [0], "851798745": [0], "851856029": [0], "851969070": [0], "852035937": [0], "852301473": [0], "853212434": [0], "853411677": [0], "853520036": [0], "853552642": [0], "853804205": [0], "853875050": [0], "853963405": [0], "854084264": [0], "854305413": [0], "854555531": [0], "854636055": [0, 1], "854725329": [0], "854734071": [0], "854761845": [0], "854851850": [0], "854960105": [0], "855036028": [0], "855141683": [0, 1], "855649502": [0], "856046738": [0], "856218778": [0], "856462178": [0, 1], "856605535": [0], "856710562": [0], "856825352": [0], "857017406": [0], "857034578": [0], "857285119": [0], "857314500": [0, 1], "857543739": [0], "857691555": [0], "857873603": [0], "858051356": [0], "858252340": [0], "858259822": [0], "858489210": [0], "858503937": [0], "858575241": [0], "858748115": [0], "859034175": [0], "859266692": [0], "859690265": [0], "860069949": [0], "860239100": [0], "860413053": [0], "860517034": [0, 1], "860782039": [0], "861297561": [0], "861915276": [0], "862332551": [0], "863160132": [0], "863205350": [0], "863747809": [0], "863857822": [0], "863960394": [0], "864402143": [0], "864577792": [0], "864838816": [0], "865338668": [0], "865349808": [0], "865499876": [0], "865843817": [0, 1], "866109161": [0], "866121035": [0], "866561114": [0], "866569283": [0], "866707540": [0], "866726669": [0], "866917487": [0], "867207597": [0], "867361562": [0, 1], "867481238": [0], "867536067": [0], "867626132": [0], "867728886": [0, 1], "867732027": [0], "867818437": [0], "867878395": [0], "868119166": [0], "868273402": [0], "868548226": [0], "868777415": [0], "868782607": [0], "869094638": [0], "869448500": [0], "869879505": [0], "870044563": [0], "870946432": [0], "871949073": [0], "872515340": [0], "872957874": [0, 1], "873033460": [0], "873145593": [0], "873514405": [0], "874817015": [0], "875283690": [0], "875307802": [0], "875406918": [0], "875409460": [0], "875476101": [0], "875910001": [0], "875968751": [0], "876528993": [0], "877608395": [0], "877708984": [0], "878333924": [0, 1], "878462223": [0], "878751959": [0], "879200143": [0], "879211034": [0], "879483098": [0], "879484181": [0], "879540518": [0], "879551558": [0], "880172015": [0], "880396451": [0], "881103966": [0], "881281055": [0], "881524743": [0], "881607124": [0], "881780171": [0], "881857715": [0], "881907456": [0], "881918791": [0], "882292309": [0, 1], "882319223": [0], "882413504": [0, 1], "882715916": [0], "882729458": [0], "883299315": [0, 1], "883448552": [0], "883585723": [0], "883651382": [0], "883905178": [0], "884318379": [0], "884345896": [0], "885184593": [0], "885331688": [0], "885398899": [0], "885837978": [0], "885895016": [0, 1], "886024357": [0], "886114048": [0], "886208112": [0], "886558255": [0], "886572120": [0], "886584937": [0], "886823915": [0], "887104066": [0], "887139369": [0], "887219564": [0], "887321540": [0], "887487697": [0], "887664502": [0], "887679344": [0], "887770073": [0], "888094435": [0], "888153025": [0], "888167240": [0], "888235014": [0], "889008628": [0], "889326164": [0], "889351220": [0, 1], "889491533": [0], "889633033": [0], "889881761": [0], "890300653": [0], "890312551": [0], "890334703": [0], "890468455": [0], "890574728": [0], "890827437": [0], "891063061": [0], "891109371": [0], "891161841": [0], "891187018": [0], "891215133": [0], "891269138": [0], "891358279": [0], "891381934": [0], "891478259": [0], "891603461": [0], "891669473": [0], "891775016": [0], "892240939": [0, 1], "893724977": [0], "893771379": [0], "893921874": [0], "894019271": [0], "894035190": [0], "894460454": [0], "894751376": [0], "894940920": [0], "894996755": [0], "895113435": [0], "895119019": [0], "895296213": [0], "895334783": [0], "895335256": [0], "895523021": [0], "895742373": [0], "895848500": [0], "895860109": [0], "895931112": [0], "896895368": [0], "896977047": [0], "897095676": [0], "897464834": [0], "898314422": [0], "898518262": [0], "898825206": [0], "899134500": [0], "899261135": [0], "900148476": [0], "900152899": [0], "900408798": [0, 1], "900446758": [0], "900836510": [0], "900874414": [0], "900962181": [0, 1], "901431113": [0], "902187033": [0], "902223203": [0], "902285817": [0], "902581160": [0], "902854992": [0], "903268318": [0], "903315257": [0, 1], "903350307": [0], "903652847": [0], "903774411": [0], "903884146": [0], "903928385": [0], "903989402": [0], "904083191": [0], "904111568": [0], "904640875": [0], "904744722": [0], "904746126": [0], "904850813": [0], "905230021": [0], "905308739": [0], "905409100": [0], "905508347": [0], "906247454": [0], "906501999": [0], "906635722": [0], "906835898": [0], "907434352": [0], "907522167": [0], "907833518": [0], "907993257": [0], "908015913": [0], "908069522": [0], "908168630": [0], "908480157": [0], "908535109": [0], "908669371": [0], "908677846": [0, 1], "908768263": [0], "908773714": [0], "908981051": [0], "909040519": [0], "909493903": [0], "909765822": [0], "909826828": [0], "910336593": [0], "910353931": [0, 1], "910524948": [0], "910601803": [0], "910668557": [0], "911021482": [0], "911341188": [0], "911699541": [0], "911852067": [0], "911889276": [0], "912046248": [0], "912181158": [0, 1], "912185362": [0], "912379487": [0], "912403412": [0, 1], "913771842": [0], "914121264": [0], "914144596": [0, 1], "914305620": [0], "914881246": [0], "914884146": [0], "915185180": [0], "915402668": [0], "915514504": [0], "915521018": [0], "915670365": [0], "915696988": [0], "915731512": [0], "915910184": [0], "916137632": [0], "916399765": [0], "916407056": [0, 1], "916960550": [0], "916963594": [0], "917298649": [0], "917681987": [0], "918190866": [0], "918266770": [0], "918720627": [0], "918772455": [0], "918814915": [0, 1], "918838724": [0], "918909540": [0], "919405662": [0], "919559357": [0], "919980161": [0], "920666492": [0], "920942052": [0], "921195733": [0], "921477302": [0], "921613909": [0], "921696711": [0], "922012722": [0], "922069311": [0], "923017593": [0], "923026393": [0], "923874721": [0], "923990228": [0], "924012836": [0], "924019068": [0], "924348183": [0], "924463850": [0], "924979758": [0], "925077849": [0], "925383032": [0], "925474332": [0], "925612333": [0], "925993496": [0], "925997171": [0], "926043441": [0], "926119604": [0], "926619745": [0], "926973576": [0], "926977055": [0], "927178320": [0], "927193347": [0], "927412335": [0], "928450092": [0], "928460705": [0], "928673400": [0], "929108004": [0], "929488228": [0], "929693771": [0], "929708964": [0], "929739161": [0], "930331266": [0], "930933096": [0], "931000889": [0], "931376420": [0, 1], "931778485": [0], "931930641": [0, 1], "932059550": [0], "932295212": [0], "932428407": [0], "932603012": [0], "932686751": [0], "932719848": [0], "932904915": [0], "933068421": [0], "933353926": [0], "933568906": [0], "933692792": [0], "933888087": [0], "934074577": [0], "934116969": [0], "934711265": [0], "934738595": [0], "935250060": [0], "935307922": [0], "935840283": [0], "935862800": [0], "935889015": [0], "935896056": [0], "936548381": [0], "936556239": [0], "936625454": [0], "936689320": [0], "936847202": [0], "936885647": [0], "937056384": [0], "937301630": [0], "937439199": [0], "937475338": [0], "937716272": [0], "937972950": [0], "938111288": [0], "938429269": [0, 1], "938438110": [0], "938592487": [0], "938956617": [0], "939001671": [0], "939319790": [0], "939702470": [0], "939926395": [0], "939968110": [0], "940008337": [0], "940027792": [0], "940113386": [0], "940139479": [0], "940405267": [0, 1], "941402171": [0], "941542267": [0], "941597664": [0], "941823044": [0], "942027129": [0], "942070225": [0], "942191296": [0], "942285087": [0], "942475556": [0], "942489302": [0], "942611222": [0], "942907809": [0], "943245938": [0], "943859789": [0], "943937972": [0], "943993709": [0], "944044571": [0], "944107393": [0], "944548718": [0], "944630382": [0, 1], "944759885": [0], "944862110": [0], "944931265": [0], "944939750": [0], "945084840": [0], "946153592": [0], "946434464": [0], "946656907": [0], "946834009": [0], "947219910": [0], "947270634": [0], "947764935": [0], "947808404": [0], "947824030": [0], "948183311": [0], "948303854": [0], "948580886": [0], "948656161": [0], "948771135": [0], "949254613": [0], "949349445": [0], "949541579": [0], "949619204": [0], "950171456": [0], "951119590": [0], "951402936": [0], "952068601": [0], "952155863": [0], "952201406": [0], "952264463": [0], "952376168": [0, 1], "952867363": [0], "953276053": [0], "953317256": [0], "953508944": [0], "953696580": [0], "953749726": [0], "953915935": [0], "954109177": [0], "954296090": [0], "954651793": [0], "954823135": [0], "954913621": [0], "955429163": [0], "956065064": [0], "956408425": [0, 1], "956605916": [0, 1], "956890196": [0], "956951769": [0], "957434726": [0], "957841143": [0], "957913699": [0], "958045220": [0], "958118786": [0, 1], "958145804": [0], "958220629": [0], "958946675": [0], "959220010": [0], "959320651": [0], "959601782": [0], "959911738": [0], "960078695": [0], "960356216": [0], "960377133": [0], "960539092": [0], "960735200": [0], "961022344": [0], "961249334": [0], "961426463": [0], "961503714": [0], "961514248": [0], "962109546": [0], "962123578": [0], "962766256": [0], "962788218": [0], "962822793": [0], "963204683": [0], "963392793": [0], "963630501": [0], "963891081": [0], "963987166": [0], "964449111": [0], "964455410": [0], "964560888": [0], "964710051": [0], "964712522": [0], "964713542": [0], "964857102": [0], "965143011": [0], "965578486": [0], "965662563": [0], "965965415": [0], "966019354": [0], "966202873": [0], "966475251": [0], "966686612": [0], "966991995": [0], "967096253": [0], "967664519": [0], "969008783": [0, 1], "969278932": [0, 1], "969295600": [0], "969377712": [0], "969623039": [0], "969980803": [0], "970606432": [0], "970992603": [0], "971049519": [0], "971056042": [0], "971692925": [0], "972035971": [0], "972198264": [0], "972402684": [0], "972647093": [0, 1], "972724924": [0], "973200842": [0], "973215641": [0], "973381066": [0], "973521518": [0], "973626692": [0], "973945493": [0], "974104658": [0], "974341953": [0], "975059981": [0], "975195514": [0, 1], "975326168": [0], "975660789": [0], "975734088": [0], "975916504": [0], "975958794": [0], "976308434": [0], "976325318": [0], "976436996": [0], "976452432": [0, 1], "976567863": [0], "976903116": [0], "977093037": [0], "977097129": [0], "977426586": [0], "977855956": [0], "978240312": [0], "978392665": [0], "978406949": [0], "979255224": [0], "979528136": [0], "979722793": [0, 1], "979977375": [0], "980407366": [0], "980585385": [0], "980955672": [0], "981021635": [0], "981153743": [0, 1], "981223215": [0], "981387142": [0], "981930987": [0], "982515520": [0], "982622116": [0], "982937802": [0], "983046111": [0], "983478401": [0], "983489705": [0], "983578947": [0], "983963434": [0], "984047239": [0], "984181188": [0], "984395859": [0], "984986296": [0], "985187195": [0], "985274297": [0, 1], "985328235": [0, 1], "985670323": [0], "986147074": [0, 1], "986675008": [0], "986699725": [0], "986957352": [0], "987081571": [0], "987496573": [0], "987558526": [0], "987830085": [0], "987838768": [0], "988052537": [0], "988316791": [0], "988342385": [0], "988408127": [0], "988504857": [0], "989355623": [0], "989400249": [0], "990012797": [0], "990552155": [0], "990929681": [0, 1], "991477753": [0], "991695290": [0], "991741356": [0], "991869861": [0, 1], "992434063": [0], "992521239": [0], "992668489": [0], "992798034": [0, 1], "992861455": [0], "993869932": [0], "993895828": [0], "994617985": [0], "994780494": [0], "995086346": [0], "995280233": [0], "995286475": [0], "995347586": [0], "995796738": [0], "995804062": [0], "995818356": [0], "995997504": [0], "996378667": [0, 1], "996751689": [0], "996931036": [0], "997759813": [0], "998150133": [0], "998279493": [0], "998620195": [0], "998645422": [0], "998755152": [0], "998836766": [0], "998921734": [0], "999101344": [0], "999638670": [0], "999740454": [0], "999761305": [0], "1001321704": [0], "1001379588": [0], "1001439864": [0], "1001628054": [0], "1001839249": [0], "1001940800": [0], "1002021539": [0], "1002048722": [0], "1002480048": [0], "1002533876": [0, 1], "1002732107": [0], "1003508382": [0], "1003787141": [0], "1003957327": [0], "1004402393": [0], "1004404730": [0], "1004414856": [0], "1004637313": [0], "1004781383": [0], "1004924355": [0], "1005129105": [0], "1005385830": [0, 1], "1005716842": [0], "1006523598": [0], "1006903223": [0], "1007068642": [0], "1007203602": [0], "1007592495": [0], "1007665556": [0], "1007713803": [0, 1], "1007853491": [0], "1007927305": [0], "1008347794": [0], "1008761029": [0], "1008879279": [0], "1009124101": [0], "1009366361": [0], "1009457746": [0], "1009885979": [0], "1009960562": [0], "1009965986": [0], "1010145043": [0], "1010191081": [0], "1010648678": [0], "1011132552": [0], "1011366626": [0, 1], "1011760379": [0], "1012194657": [0], "1012425835": [0], "1012449714": [0], "1012632282": [0], "1012869728": [0], "1013261524": [0], "1013337206": [0], "1013781328": [0], "1014441315": [0], "1014569233": [0], "1014710491": [0], "1014916849": [0], "1014933783": [0], "1015764264": [0], "1015767529": [0], "1016311996": [0], "1016391394": [0], "1016657076": [0], "1016779882": [0], "1016799555": [0], "1016875129": [0], "1016962163": [0], "1017174958": [0, 1], "1017368557": [0, 1], "1017551285": [0], "1017606307": [0], "1017645186": [0], "1018043519": [0], "1018353911": [0], "1018639577": [0], "1018694034": [0], "1019118223": [0], "1019137477": [0, 1], "1019172035": [0], "1019291684": [0], "1019591558": [0], "1019813644": [0], "1019826575": [0], "1019955764": [0, 1], "1020093984": [0], "1020125184": [0], "1020245565": [0], "1020466574": [0], "1020580717": [0], "1020746448": [0], "1020871579": [0], "1021016920": [0], "1021569125": [0, 1], "1021625666": [0], "1022340636": [0, 1], "1022614221": [0], "1022638276": [0], "1022817902": [0], "1022908576": [0], "1022981393": [0], "1023657402": [0], "1023713785": [0], "1024633652": [0], "1025132028": [0], "1025276735": [0], "1025284110": [0], "1025331163": [0], "1025650932": [0], "1025724420": [0], "1026190659": [0], "1026444429": [0, 1], "1026456357": [0], "1026489497": [0, 1], "1027002839": [0], "1027015416": [0], "1027083067": [0], "1027142174": [0], "1027195587": [0], "1027389205": [0], "1027467567": [0], "1027748880": [0], "1028090059": [0], "1028334301": [0], "1028484905": [0], "1028702872": [0], "1029185332": [0], "1029974028": [0], "1030338502": [0], "1030603328": [0], "1030605858": [0], "1030755249": [0], "1031214029": [0], "1031609111": [0], "1031700995": [0], "1031780164": [0], "1031808887": [0], "1032077126": [0, 1], "1032243556": [0], "1032575492": [0], "1032618472": [0], "1032944455": [0], "1033091107": [0], "1033723890": [0], "1033889273": [0, 1], "1033978554": [0], "1034246254": [0], "1034557333": [0], "1034750700": [0], "1034789190": [0], "1035183004": [0], "1035347266": [0], "1035392140": [0], "1035441192": [0], "1035504263": [0], "1035641299": [0], "1035668174": [0], "1035866463": [0], "1036033475": [0], "1036728498": [0], "1036899221": [0], "1036986092": [0], "1037705965": [0], "1037771593": [0], "1037986054": [0], "1038024769": [0], "1038431889": [0], "1038735411": [0], "1039213056": [0], "1039271251": [0], "1039682822": [0], "1040234634": [0], "1040333046": [0], "1040335423": [0], "1040374306": [0], "1040429961": [0], "1040693197": [0], "1040925141": [0], "1041130262": [0], "1041202164": [0], "1041862021": [0], "1042087334": [0], "1042112998": [0], "1042505492": [0], "1042787669": [0], "1043012980": [0], "1043065353": [0], "1043259152": [0], "1043551286": [0], "1043931806": [0], "1044386546": [0], "1044572174": [0], "1044592796": [0], "1044685710": [0], "1044855948": [0], "1044967296": [0], "1044970843": [0], "1045671912": [0], "1046255719": [0], "1046630579": [0], "1046810449": [0], "1047075859": [0], "1047283463": [0], "1048061405": [0], "1048229518": [0], "1048611277": [0], "1048786046": [0], "1049008249": [0], "1050017091": [0], "1050206791": [0], "1050230956": [0], "1050246959": [0], "1050450435": [0], "1050924448": [0], "1051269891": [0], "1052146037": [0], "1052252659": [0], "1052674580": [0], "1052883465": [0], "1053633501": [0], "1053651932": [0], "1053726436": [0], "1053791993": [0], "1053945305": [0], "1054325811": [0], "1054527164": [0], "1054595658": [0], "1054646610": [0], "1054786609": [0, 1], "1055085816": [0], "1055513774": [0], "1055573766": [0], "1055640610": [0], "1056407955": [0], "1056458939": [0], "1056626485": [0], "1056916875": [0], "1056929908": [0], "1057164815": [0], "1057234496": [0], "1057988182": [0, 1], "1057990853": [0], "1058095917": [0], "1058250386": [0], "1058427974": [0], "1058428454": [0], "1058498181": [0], "1059032253": [0], "1059166610": [0], "1059473661": [0], "1059734488": [0], "1060097092": [0], "1060222930": [0], "1060324714": [0], "1060740194": [0], "1060773690": [0], "1060837347": [0], "1060895953": [0], "1061026664": [0], "1061096141": [0], "1061251555": [0], "1061516794": [0], "1061595949": [0], "1061635143": [0], "1061893653": [0], "1062110280": [0, 1], "1062313634": [0], "1062475239": [0], "1062748151": [0], "1062910340": [0], "1063026619": [0], "1063088693": [0], "1063408439": [0], "1063479487": [0], "1063766075": [0], "1064170935": [0], "1064324202": [0], "1064339179": [0], "1064412409": [0], "1064728241": [0], "1064912006": [0], "1064965474": [0], "1065275713": [0], "1065724953": [0], "1066024311": [0], "1066474054": [0], "1066678208": [0, 1], "1066719973": [0], "1066826043": [0], "1067016169": [0], "1067017122": [0], "1067433764": [0], "1067530172": [0], "1067784250": [0], "1067933756": [0], "1068130840": [0], "1069613056": [0, 1], "1069629787": [0], "1069952809": [0], "1070094867": [0], "1070147528": [0], "1070423795": [0], "1070501206": [0], "1070641524": [0], "1070681391": [0], "1070709067": [0], "1070919811": [0], "1071306940": [0], "1071625287": [0], "1071674582": [0], "1071951690": [0], "1072124350": [0], "1072209210": [0], "1072871021": [0, 1], "1073819557": [0], "1073864941": [0], "1074162609": [0], "1074245044": [0], "1074553117": [0], "1074909897": [0, 1], "1075376757": [0], "1075401985": [0], "1075603568": [0], "1075610990": [0], "1075662188": [0], "1076033196": [0], "1076375652": [0], "1076478212": [0], "1076620079": [0], "1076669911": [0, 1], "1076806480": [0], "1077022445": [0], "1077116183": [0], "1077135907": [0], "1077260773": [0], "1077524580": [0], "1077781545": [0], "1077913570": [0], "1078460162": [0], "1078623712": [0], "1079027209": [0], "1079191280": [0], "1079392409": [0], "1079416456": [0], "1079444177": [0], "1079608902": [0], "1079921172": [0], "1080274556": [0], "1080542429": [0], "1081004638": [0], "1081045979": [0], "1081137638": [0], "1081156235": [0], "1081417698": [0], "1081627805": [0], "1081671496": [0], "1081784052": [0], "1081875845": [0, 1], "1081933930": [0], "1081956911": [0], "1082154133": [0], "1082186351": [0, 1], "1082346379": [0], "1082461320": [0], "1082615620": [0], "1083522093": [0], "1083564178": [0], "1083654127": [0], "1083674102": [0], "1083772754": [0], "1083829590": [0], "1084311592": [0], "1084409094": [0], "1084423374": [0], "1084463968": [0], "1084898238": [0], "1084996718": [0], "1085148763": [0], "1085176158": [0], "1085253891": [0], "1085347750": [0], "1085524274": [0], "1085853575": [0, 1], "1085938795": [0], "1085981029": [0], "1086326539": [0], "1086383545": [0], "1086497801": [0], "1086509443": [0], "1086584413": [0], "1086804959": [0], "1087107746": [0], "1087224800": [0], "1087246203": [0], "1087471183": [0], "1087566834": [0], "1087611256": [0], "1088181610": [0], "1088199148": [0], "1088312211": [0], "1088336960": [0], "1088486994": [0], "1088685646": [0], "1088841058": [0], "1089136257": [0], "1089216930": [0], "1089248832": [0], "1089293858": [0], "1089548926": [0], "1090285235": [0], "1090501053": [0], "1090557401": [0], "1090811858": [0], "1091139102": [0], "1091263381": [0], "1091620435": [0], "1091694562": [0], "1091753713": [0], "1091800078": [0], "1091851273": [0], "1092288497": [0], "1092463518": [0], "1093062389": [0], "1093115550": [0], "1093179429": [0], "1093209510": [0], "1093425554": [0], "1093768189": [0], "1093947693": [0], "1094000974": [0], "1094037331": [0], "1094732219": [0], "1095265101": [0], "1095381162": [0], "1095440820": [0], "1095485734": [0], "1095533217": [0], "1095537240": [0, 1], "1095690405": [0], "1095839614": [0], "1096047865": [0], "1096436039": [0], "1096808458": [0], "1096818669": [0, 1], "1096860313": [0], "1097484072": [0], "1097746459": [0], "1098005491": [0], "1098501905": [0], "1098529926": [0], "1098690479": [0], "1098852493": [0], "1099049304": [0], "1099108173": [0], "1099194238": [0], "1099354992": [0], "1099505255": [0], "1099548088": [0], "1100014184": [0], "1100237998": [0], "1100245951": [0], "1100609729": [0], "1101027910": [0], "1101156350": [0], "1101516277": [0], "1102058992": [0], "1102224387": [0], "1102225807": [0], "1102454126": [0], "1102807357": [0], "1102966095": [0], "1103467230": [0], "1103488371": [0], "1103524716": [0], "1103915756": [0], "1104332885": [0], "1104391256": [0], "1104911057": [0], "1105236965": [0], "1105660433": [0], "1105806318": [0], "1105824183": [0], "1105961340": [0], "1106052114": [0], "1106167897": [0], "1106267814": [0], "1106366979": [0], "1107182560": [0], "1107283088": [0], "1107462932": [0], "1107530094": [0], "1107671697": [0], "1107834178": [0], "1107924739": [0, 1], "1108001341": [0], "1108030458": [0], "1108296269": [0], "1108608333": [0], "1109064362": [0], "1109634523": [0], "1109719117": [0], "1109776806": [0], "1109806339": [0], "1109966523": [0], "1110476555": [0], "1110517959": [0], "1110536866": [0], "1110631618": [0], "1110665772": [0], "1110752199": [0], "1110942214": [0], "1111064667": [0], "1111071955": [0], "1111268325": [0], "1111910866": [0], "1112045744": [0, 1], "1112306911": [0], "1112600696": [0], "1112985147": [0], "1113780070": [0], "1114003339": [0], "1114043742": [0, 1], "1114268043": [0], "1114380013": [0, 1], "1114426603": [0], "1114647611": [0], "1114704621": [0], "1114766557": [0], "1114930921": [0], "1115068094": [0], "1115277544": [0], "1116058404": [0], "1116201959": [0], "1116288795": [0], "1116470998": [0], "1116479642": [0], "1116954243": [0], "1117129614": [0], "1117191180": [0], "1117257441": [0], "1117453190": [0], "1117960821": [0], "1118217753": [0], "1119072566": [0], "1119164004": [0], "1119433914": [0], "1119525164": [0], "1119572304": [0], "1119643772": [0], "1119726295": [0], "1119848097": [0], "1119998851": [0], "1120830595": [0], "1121112895": [0], "1122041276": [0, 1], "1123018542": [0], "1123351701": [0], "1123437844": [0], "1124249988": [0], "1124302035": [0], "1124346051": [0], "1124530826": [0], "1124574582": [0], "1125250281": [0], "1125804894": [0], "1125870120": [0], "1126500633": [0], "1126827577": [0], "1126914912": [0], "1126938487": [0, 1], "1126955140": [0], "1127836696": [0], "1128034694": [0], "1128114488": [0], "1128410094": [0], "1129191551": [0], "1129344668": [0], "1129399128": [0], "1129513632": [0], "1129543757": [0], "1129617338": [0], "1129895803": [0], "1130343402": [0], "1130377418": [0], "1130599379": [0], "1130880791": [0], "1130987733": [0], "1131114042": [0], "1131231818": [0], "1131279135": [0], "1132502971": [0], "1132714048": [0], "1132729550": [0], "1132773168": [0, 1], "1132782521": [0], "1132784821": [0, 1], "1133273412": [0], "1133549992": [0], "1133687943": [0], "1133982658": [0], "1134054930": [0], "1134067844": [0], "1134271334": [0, 1], "1134793801": [0], "1135126065": [0], "1135818486": [0], "1135871862": [0], "1136053250": [0], "1136631178": [0], "1136743062": [0], "1137292267": [0], "1137634747": [0], "1137708667": [0], "1137754680": [0], "1138240781": [0], "1138677916": [0], "1138747569": [0], "1138795272": [0], "1139018666": [0], "1139034625": [0], "1139603417": [0], "1139610541": [0], "1139795169": [0], "1139854235": [0], "1139874109": [0], "1139973603": [0], "1140630064": [0, 1], "1141129708": [0], "1141258931": [0], "1141266634": [0], "1141796599": [0], "1141846828": [0], "1141958168": [0], "1141996138": [0], "1142068242": [0], "1142240670": [0], "1142257633": [0], "1142903083": [0], "1142997506": [0], "1143233578": [0], "1143438182": [0], "1143700024": [0], "1144092947": [0, 1], "1144156039": [0], "1144213362": [0], "1144393411": [0], "1144698841": [0], "1144893851": [0], "1144932134": [0], "1144958726": [0, 1], "1145001230": [0], "1145055573": [0], "1145547480": [0], "1145617744": [0], "1145707206": [0], "1146185446": [0], "1146348640": [0], "1146554698": [0], "1146705637": [0], "1146731587": [0, 1], "1146928807": [0], "1146941516": [0], "1147184191": [0], "1147263168": [0], "1147454481": [0], "1147834924": [0], "1148371391": [0, 1], "1149042163": [0], "1149419835": [0], "1149434948": [0], "1149800107": [0], "1149858271": [0], "1149964515": [0], "1149993154": [0], "1150191201": [0], "1150250029": [0], "1150257607": [0], "1150771679": [0], "1150810199": [0], "1151124153": [0], "1151232444": [0], "1151402172": [0], "1151483734": [0], "1151842009": [0], "1151853184": [0], "1151893470": [0], "1152201937": [0], "1152251204": [0], "1152540986": [0], "1152576928": [0], "1152617188": [0], "1152653418": [0], "1152816023": [0], "1153199694": [0], "1153652093": [0], "1153789489": [0], "1153839350": [0], "1154433592": [0], "1154715850": [0], "1154820509": [0], "1155428815": [0], "1155523168": [0], "1155801624": [0], "1155901301": [0], "1156711004": [0], "1156821103": [0], "1157425675": [0], "1157646626": [0], "1157781806": [0], "1157944553": [0], "1158074926": [0], "1158192732": [0], "1158528617": [0], "1158635803": [0, 1], "1158795410": [0], "1158971690": [0], "1159439932": [0], "1159721449": [0], "1159807657": [0], "1159878588": [0], "1160066590": [0, 1], "1160098501": [0, 1], "1160749108": [0], "1161007240": [0], "1161223128": [0], "1161265800": [0], "1161496522": [0], "1161751815": [0], "1161840099": [0], "1162287035": [0], "1162338270": [0], "1163251439": [0], "1163269852": [0], "1163837800": [0], "1163855995": [0], "1164180201": [0], "1164640014": [0], "1164754507": [0], "1165190597": [0], "1165200997": [0], "1165336550": [0], "1165862978": [0], "1166005492": [0], "1166179146": [0], "1166823604": [0], "1166876093": [0], "1167095990": [0], "1167525040": [0], "1167525740": [0], "1167574470": [0], "1167589978": [0], "1167599699": [0], "1167660003": [0], "1168328085": [0], "1168413288": [0], "1168559836": [0], "1168819154": [0], "1168897654": [0], "1169039746": [0], "1169091497": [0], "1169127760": [0], "1169287625": [0], "1169803901": [0], "1169943221": [0], "1169948392": [0], "1170142828": [0], "1170248617": [0, 1], "1170583284": [0], "1170614179": [0], "1170656221": [0], "1170775879": [0], "1170778152": [0], "1171351949": [0], "1171437287": [0], "1171652344": [0], "1172316338": [0], "1172533533": [0], "1172698971": [0], "1172833650": [0], "1172933560": [0], "1173448157": [0], "1173730780": [0], "1174416203": [0], "1174603693": [0], "1174708262": [0], "1174741392": [0], "1174797944": [0], "1174891637": [0], "1174966055": [0], "1174997245": [0], "1175138349": [0], "1175342061": [0], "1175464017": [0], "1175524808": [0], "1175686219": [0, 1], "1175727164": [0], "1175814164": [0], "1176302842": [0, 1], "1176314427": [0], "1176645356": [0], "1176807112": [0], "1177122323": [0], "1177126323": [0], "1177194111": [0], "1177201966": [0], "1177458954": [0, 1], "1177816768": [0], "1177865920": [0], "1177933804": [0], "1178282336": [0], "1178466829": [0], "1178612279": [0], "1178714831": [0], "1178719329": [0], "1178723478": [0], "1178801954": [0], "1178879173": [0], "1180054197": [0], "1180075504": [0], "1180281871": [0], "1180348440": [0], "1180413056": [0], "1180501841": [0], "1181337523": [0, 1], "1181551924": [0], "1181658279": [0], "1182726028": [0], "1182958203": [0], "1182994491": [0], "1183213178": [0], "1183514062": [0], "1183790571": [0], "1184051727": [0], "1184475283": [0], "1184594214": [0], "1185223037": [0], "1185796428": [0], "1186198313": [0], "1186354660": [0], "1186579889": [0], "1186605878": [0], "1187347270": [0], "1187588399": [0], "1187825793": [0], "1187951309": [0], "1188256050": [0], "1188300321": [0], "1188380708": [0], "1188948225": [0], "1189079918": [0], "1189187797": [0], "1189474430": [0], "1189777355": [0], "1189877072": [0], "1190299362": [0], "1190744374": [0], "1191390253": [0], "1191402651": [0], "1191594663": [0], "1192028488": [0], "1192683173": [0], "1192868071": [0], "1192943808": [0], "1193005586": [0], "1193164170": [0], "1193187154": [0], "1193224615": [0], "1194345989": [0], "1194682790": [0, 1], "1195220320": [0], "1195319105": [0, 1], "1195506231": [0], "1195655539": [0], "1195785922": [0], "1195934782": [0], "1196128928": [0], "1196240030": [0], "1196382759": [0], "1196575159": [0], "1196590957": [0], "1196826917": [0], "1197057496": [0], "1197084631": [0], "1197122521": [0], "1197698735": [0], "1197915655": [0], "1198087769": [0], "1198129497": [0], "1198327027": [0], "1198628042": [0], "1198720611": [0], "1199312698": [0], "1200353220": [0], "1200525702": [0], "1200556287": [0], "1200600810": [0, 1], "1200629228": [0], "1200643843": [0], "1200817001": [0], "1201076961": [0], "1201127101": [0], "1201197373": [0], "1201766600": [0], "1201970333": [0], "1202264884": [0], "1202544641": [0, 1], "1202692254": [0], "1202833871": [0], "1202885063": [0], "1203212722": [0], "1203314960": [0], "1203361057": [0], "1203373524": [0], "1203585655": [0], "1203715511": [0], "1203764156": [0], "1203780435": [0], "1203802163": [0], "1203881123": [0], "1203929684": [0], "1204060962": [0], "1204832815": [0], "1205118459": [0], "1205215798": [0], "1205579560": [0], "1206026635": [0], "1206093401": [0], "1206401877": [0], "1206812696": [0], "1206928635": [0, 1], "1207069085": [0], "1207108828": [0, 1], "1207297260": [0], "1207412742": [0], "1208158300": [0], "1208234166": [0], "1208235485": [0], "1208320048": [0], "1208407558": [0], "1208561267": [0], "1210083808": [0], "1210215905": [0], "1210538624": [0, 1], "1210727161": [0], "1210903338": [0], "1211061272": [0], "1211096388": [0], "1211508367": [0], "1211609958": [0], "1211723640": [0], "1212266098": [0], "1212302941": [0, 1], "1212347271": [0], "1212355493": [0], "1212479302": [0], "1212584260": [0], "1212672819": [0], "1212848063": [0, 1], "1212889643": [0], "1213442084": [0], "1213593062": [0], "1213896111": [0], "1214341135": [0], "1214447025": [0], "1214623489": [0, 1], "1214697558": [0, 1], "1214843141": [0], "1215866112": [0], "1216278407": [0], "1216588487": [0], "1216594517": [0], "1216660467": [0], "1216715653": [0], "1216906944": [0], "1216950303": [0], "1217011240": [0], "1217080209": [0], "1217098943": [0], "1217193523": [0], "1217286262": [0], "1218253784": [0], "1218712449": [0], "1218880713": [0], "1219210669": [0], "1219217293": [0], "1219253268": [0], "1219588628": [0], "1219883946": [0], "1220199342": [0], "1220282975": [0], "1220293223": [0], "1220293637": [0], "1220421701": [0], "1220504606": [0], "1220540396": [0], "1220723535": [0], "1220820713": [0], "1221014965": [0], "1221086724": [0], "1221116652": [0], "1221233489": [0], "1221271994": [0], "1221286068": [0], "1221304624": [0], "1221523434": [0], "1221728149": [0], "1221939122": [0], "1222686065": [0], "1222686487": [0], "1222818674": [0], "1223029075": [0], "1223157820": [0], "1223229698": [0], "1223354641": [0], "1223431532": [0], "1223883955": [0, 1], "1224194050": [0], "1224395836": [0], "1224454427": [0], "1224477055": [0], "1224493415": [0], "1224591673": [0, 1], "1224817075": [0], "1224916249": [0], "1224929473": [0], "1225620834": [0], "1225726656": [0], "1225742635": [0], "1225791482": [0], "1225987897": [0], "1225992133": [0], "1226111743": [0], "1226557317": [0], "1227280931": [0], "1227338623": [0], "1227535473": [0], "1227693695": [0], "1227818402": [0], "1227966556": [0], "1228655780": [0], "1228941255": [0], "1229072851": [0], "1229260061": [0, 1], "1229356632": [0], "1229716664": [0], "1230176892": [0], "1230351072": [0], "1230389522": [0], "1230454377": [0], "1230565183": [0], "1230655753": [0, 1], "1231353018": [0], "1231470595": [0], "1231622061": [0], "1232235085": [0], "1232379346": [0], "1232529554": [0], "1232594991": [0], "1232760047": [0], "1233376873": [0], "1233403435": [0], "1233638800": [0], "1233664413": [0], "1233808587": [0], "1235032809": [0], "1235606504": [0], "1236328654": [0, 1], "1236508099": [0], "1236514645": [0], "1237308377": [0], "1237496085": [0], "1237525746": [0], "1237753516": [0], "1238263679": [0], "1238346888": [0], "1238605045": [0], "1238901541": [0], "1239015424": [0], "1239595157": [0], "1239657886": [0], "1239730200": [0], "1239736416": [0], "1239846231": [0], "1239953012": [0], "1240148037": [0, 1], "1240403939": [0], "1240564809": [0], "1240743760": [0], "1240973565": [0], "1241353704": [0], "1241422221": [0], "1241821134": [0], "1242075900": [0], "1242194097": [0], "1242505486": [0], "1243213084": [0], "1243239912": [0], "1243513752": [0], "1243534580": [0], "1243604310": [0], "1244642872": [0], "1244777270": [0], "1244780595": [0], "1244852739": [0], "1245415637": [0], "1245490855": [0], "1246020236": [0], "1246036579": [0], "1246087400": [0], "1246619991": [0], "1247332922": [0], "1247355191": [0], "1247598996": [0], "1247653859": [0], "1247756240": [0], "1247789002": [0], "1247999345": [0], "1248042198": [0, 1], "1248714210": [0], "1248881101": [0], "1248884163": [0], "1249001441": [0], "1249292596": [0], "1249383426": [0], "1249393679": [0], "1249437302": [0], "1249829850": [0, 1], "1249956039": [0], "1250001358": [0], "1250145795": [0], "1250213467": [0], "1250437073": [0], "1250703741": [0], "1250743281": [0], "1250921743": [0], "1251008655": [0], "1251048415": [0, 1], "1251179752": [0], "1251236700": [0], "1251378279": [0], "1251716937": [0], "1252496408": [0], "1252759235": [0], "1252876605": [0], "1253028111": [0], "1253177850": [0], "1253478460": [0], "1253935351": [0], "1254243015": [0], "1255175149": [0], "1255475956": [0, 1], "1255746379": [0], "1255811744": [0], "1255957392": [0], "1255991645": [0], "1256023049": [0], "1256031999": [0], "1256171384": [0], "1256554409": [0], "1256939041": [0], "1257063408": [0], "1257090766": [0], "1257120247": [0], "1258165223": [0], "1258170392": [0], "1258433064": [0], "1258569300": [0], "1258840534": [0], "1258862138": [0], "1258945409": [0], "1259519614": [0], "1259573611": [0], "1259858252": [0], "1259975089": [0], "1260326231": [0], "1260443374": [0], "1260885738": [0], "1261021536": [0], "1261069908": [0], "1261215517": [0], "1261254574": [0], "1261280028": [0, 1], "1261343477": [0], "1261384010": [0], "1261567380": [0], "1262267418": [0], "1262868534": [0], "1263252514": [0], "1263449544": [0], "1263896185": [0], "1264282491": [0], "1264396515": [0], "1264448304": [0], "1264546275": [0], "1264628042": [0], "1265108424": [0], "1265247027": [0], "1265452511": [0], "1265508949": [0], "1265565250": [0], "1265811958": [0], "1266005842": [0], "1266066170": [0], "1266132841": [0], "1266227832": [0], "1266357172": [0], "1266854830": [0], "1266950750": [0], "1267063861": [0], "1267226893": [0], "1267331197": [0, 1], "1267394497": [0], "1267703126": [0, 1], "1267811056": [0], "1267834056": [0], "1268236624": [0], "1268550771": [0], "1268621145": [0], "1268733759": [0], "1269221384": [0], "1269286359": [0], "1269654233": [0], "1269749632": [0], "1269756847": [0], "1269848657": [0], "1270257042": [0], "1270504569": [0], "1270839890": [0], "1271648516": [0], "1272556303": [0], "1272628109": [0], "1272716083": [0], "1273015519": [0, 1], "1273120124": [0], "1273779684": [0], "1273885251": [0], "1274007643": [0], "1274272512": [0], "1274336210": [0], "1274460431": [0], "1274899323": [0], "1275706495": [0], "1275775517": [0], "1276106656": [0], "1276199291": [0], "1276288464": [0], "1276376224": [0], "1276386486": [0, 1], "1277052775": [0], "1277069074": [0], "1277504533": [0], "1277603427": [0, 1], "1277886341": [0], "1277966470": [0], "1278310355": [0], "1278492839": [0], "1278772774": [0], "1279391075": [0], "1279516874": [0], "1279838103": [0], "1279935540": [0], "1280317027": [0, 1], "1280340345": [0], "1280577810": [0], "1280700911": [0], "1280787359": [0], "1280916636": [0], "1281357499": [0], "1281469359": [0], "1281725269": [0], "1282126213": [0], "1283079932": [0], "1283106027": [0], "1283296457": [0], "1284082281": [0], "1284252924": [0], "1284257972": [0], "1284359080": [0], "1284660385": [0], "1285496594": [0], "1285509102": [0], "1285690325": [0], "1285737216": [0], "1285816072": [0], "1286500437": [0], "1286939649": [0], "1287367810": [0], "1287461476": [0], "1287475498": [0], "1288231980": [0], "1288477801": [0], "1288808216": [0], "1288820835": [0], "1289031496": [0], "1289039742": [0], "1289086363": [0], "1289153992": [0], "1289199866": [0], "1289269296": [0], "1289557266": [0], "1289845017": [0], "1290163001": [0], "1290211092": [0], "1291227546": [0], "1291335870": [0], "1291480115": [0], "1291586015": [0], "1291698446": [0], "1291866437": [0], "1291918961": [0, 1], "1292041417": [0], "1292205691": [0], "1292760902": [0], "1292960946": [0], "1293036461": [0], "1293322440": [0], "1293560512": [0], "1293576965": [0, 1], "1294223801": [0], "1294237744": [0], "1294371144": [0], "1294490578": [0], "1294518653": [0], "1294792788": [0], "1294923191": [0], "1295002813": [0], "1295010988": [0], "1295358285": [0], "1295399854": [0], "1295493639": [0], "1295731753": [0], "1296251756": [0], "1296443785": [0], "1296687560": [0], "1297101554": [0], "1297316188": [0], "1297501092": [0], "1297901705": [0, 1], "1298081335": [0], "1298825632": [0], "1298838322": [0], "1298901031": [0], "1298993732": [0], "1299523450": [0], "1299560066": [0], "1299692502": [0], "1299742046": [0], "1299841089": [0], "1300063058": [0], "1300087424": [0], "1300099271": [0, 1], "1300249543": [0], "1300277517": [0], "1300337819": [0], "1300400138": [0], "1300495403": [0], "1300751281": [0], "1301159163": [0], "1301338877": [0], "1301459958": [0], "1301486294": [0], "1301757036": [0], "1301837377": [0], "1302496538": [0], "1302597694": [0], "1302754594": [0], "1302839875": [0, 1], "1302870914": [0], "1303399499": [0], "1304711795": [0, 1], "1304754156": [0], "1304888461": [0, 1], "1304964755": [0, 1], "1305385401": [0], "1305494779": [0], "1305928851": [0], "1306466198": [0], "1306617452": [0, 1], "1306986786": [0], "1307004028": [0], "1307174163": [0], "1307891709": [0], "1308068265": [0], "1308087521": [0], "1308400735": [0], "1308401909": [0], "1308435233": [0], "1308726441": [0], "1308839116": [0], "1309364785": [0], "1309420125": [0], "1309423606": [0], "1309670276": [0], "1309705739": [0], "1310359814": [0, 1], "1310422774": [0], "1310455843": [0], "1310987634": [0], "1311011246": [0], "1311440354": [0], "1311445125": [0], "1311478267": [0], "1311759458": [0], "1311772926": [0], "1311820334": [0], "1312312226": [0], "1312346987": [0], "1312410546": [0], "1312779693": [0], "1312847716": [0], "1312882585": [0], "1313258086": [0], "1313366027": [0], "1313466218": [0], "1313689683": [0], "1313830234": [0], "1313939707": [0], "1314331021": [0], "1314520616": [0], "1314525382": [0], "1315411343": [0], "1315663051": [0], "1315933161": [0], "1316230439": [0], "1316484885": [0], "1316583100": [0], "1317303767": [0], "1317561301": [0], "1318315610": [0], "1318673271": [0], "1318798984": [0], "1319409223": [0], "1319545190": [0], "1319638841": [0], "1319876096": [0], "1320004126": [0, 1], "1320419225": [0], "1320756855": [0, 1], "1320845913": [0], "1321159338": [0], "1321400642": [0], "1321660250": [0], "1322258574": [0], "1322515540": [0], "1322582421": [0], "1322656069": [0], "1323113906": [0], "1323220563": [0], "1323236227": [0], "1323947449": [0], "1324044549": [0], "1324048125": [0], "1324223125": [0], "1324399445": [0], "1325155298": [0], "1325186555": [0], "1325338529": [0], "1325453591": [0, 1], "1325837756": [0], "1325971907": [0], "1326234155": [0], "1326447218": [0], "1326528091": [0], "1326528249": [0], "1326587249": [0], "1326588461": [0], "1326689422": [0], "1327026311": [0], "1327047900": [0], "1327670294": [0], "1327944483": [0], "1328170566": [0], "1328214590": [0], "1328531609": [0], "1329191296": [0], "1329586101": [0, 1], "1329617763": [0], "1329716093": [0], "1329806978": [0], "1330122454": [0], "1330620622": [0], "1330647161": [0], "1330906455": [0], "1331228201": [0], "1331281221": [0], "1331501077": [0], "1331727257": [0], "1332023361": [0], "1332896788": [0], "1333327594": [0], "1333371671": [0], "1333382788": [0], "1333415899": [0], "1333744078": [0], "1334134466": [0], "1334524341": [0], "1334581612": [0], "1334734332": [0], "1334818866": [0], "1335273681": [0], "1335823036": [0], "1336316813": [0], "1336805216": [0], "1337614490": [0], "1338012971": [0], "1338253590": [0], "1338263028": [0], "1338287074": [0], "1338591133": [0], "1338749040": [0], "1338937507": [0], "1339184286": [0], "1339286665": [0], "1339428917": [0], "1339517019": [0], "1339541992": [0], "1340106759": [0], "1340512098": [0], "1340869149": [0], "1341012169": [0], "1341084769": [0], "1341384704": [0], "1341406716": [0], "1341451355": [0], "1341492616": [0], "1342354426": [0], "1342424954": [0], "1342621423": [0], "1342623428": [0], "1342680306": [0], "1342768092": [0], "1342922531": [0], "1343014929": [0], "1343041890": [0], "1344037207": [0], "1344365176": [0], "1344435402": [0], "1344457708": [0], "1344486489": [0], "1345017558": [0], "1345033354": [0], "1345099156": [0], "1345446189": [0], "1345645926": [0], "1345698684": [0], "1345823186": [0], "1345901666": [0], "1346407936": [0, 1], "1346430272": [0], "1346622491": [0], "1346636721": [0], "1346957801": [0], "1347230515": [0], "1347386602": [0], "1348049146": [0, 1], "1348081414": [0], "1348709580": [0], "1349084988": [0], "1349191417": [0], "1349312373": [0], "1349537345": [0], "1350179271": [0], "1350244126": [0], "1350523645": [0], "1350577416": [0, 1], "1351366958": [0], "1351382364": [0], "1351543359": [0], "1351731185": [0, 1], "1352370138": [0], "1352484449": [0], "1352577990": [0], "1352974829": [0], "1353218479": [0], "1353387543": [0], "1353486966": [0], "1353542258": [0], "1353550465": [0], "1354099075": [0], "1354119402": [0], "1354318346": [0], "1354486567": [0], "1354625383": [0], "1354637571": [0], "1354799617": [0], "1354989552": [0], "1355477725": [0], "1355605413": [0], "1355660052": [0], "1356006433": [0], "1356049383": [0], "1356129628": [0], "1356305398": [0], "1356495913": [0], "1356805121": [0], "1356883056": [0], "1357105314": [0, 1], "1357742584": [0], "1357787208": [0], "1357963025": [0], "1358185734": [0], "1358423962": [0], "1358752795": [0], "1359616198": [0], "1359954632": [0], "1359981173": [0], "1360090406": [0], "1361113274": [0], "1361380190": [0], "1361528389": [0], "1361683723": [0], "1361843045": [0], "1362248847": [0], "1362720627": [0], "1362727020": [0], "1363032247": [0], "1363534718": [0], "1363635723": [0], "1363655156": [0], "1363928097": [0], "1364004564": [0], "1364569540": [0], "1364784934": [0, 1], "1364812652": [0], "1364899933": [0], "1365035347": [0], "1365118108": [0, 1], "1365124238": [0], "1365207976": [0], "1365614748": [0], "1365744091": [0], "1366171111": [0], "1366234550": [0], "1366562559": [0], "1366831023": [0], "1367048768": [0], "1367167251": [0], "1367572921": [0], "1367938865": [0], "1368039138": [0], "1368170570": [0], "1368480116": [0], "1368645844": [0], "1368777171": [0], "1369674058": [0], "1369842595": [0], "1370560788": [0], "1370608867": [0], "1370824085": [0], "1370830665": [0], "1371306962": [0], "1371378049": [0, 1], "1371389743": [0], "1371392852": [0], "1371638927": [0], "1371679412": [0], "1372000294": [0], "1372874548": [0], "1372922935": [0], "1373143948": [0], "1374318663": [0], "1374321762": [0], "1374443872": [0], "1374632520": [0], "1374685365": [0], "1375366257": [0], "1375846873": [0], "1376236455": [0], "1376573797": [0], "1376641738": [0], "1376645633": [0], "1376782440": [0], "1377305519": [0], "1377392628": [0], "1377411784": [0], "1377480264": [0, 1], "1377560902": [0], "1377654142": [0], "1377675241": [0], "1377961023": [0], "1377961246": [0], "1378023485": [0], "1378575272": [0], "1378667706": [0], "1379015963": [0], "1379079122": [0], "1379170426": [0], "1379454752": [0], "1379551628": [0, 1], "1379581579": [0], "1380287247": [0], "1380526949": [0, 1], "1380879733": [0], "1381160079": [0], "1381509891": [0], "1381535543": [0], "1381567860": [0, 1], "1381979651": [0], "1382346019": [0], "1382678845": [0], "1382854836": [0], "1383074885": [0], "1383103616": [0], "1383321360": [0], "1383538994": [0], "1383810049": [0], "1384510561": [0], "1384706638": [0, 1], "1385738628": [0], "1386077156": [0], "1386091811": [0], "1386212627": [0], "1386399716": [0], "1386636585": [0], "1386809689": [0], "1386956502": [0], "1387082977": [0], "1387589986": [0], "1387739075": [0], "1387819789": [0], "1387830858": [0], "1388138045": [0], "1388166897": [0, 1], "1388244450": [0], "1388370947": [0], "1388375553": [0], "1388521584": [0], "1388610433": [0], "1388701012": [0], "1388750354": [0], "1388764603": [0], "1389055744": [0], "1389089673": [0, 1], "1389130430": [0], "1389397330": [0], "1389548025": [0], "1389704398": [0], "1389999345": [0], "1390029114": [0, 1], "1390078737": [0], "1390254406": [0], "1390421021": [0], "1390499995": [0], "1390666234": [0], "1391057679": [0], "1391457057": [0], "1391465562": [0], "1391857706": [0], "1392247829": [0], "1392292471": [0], "1392321288": [0], "1392341747": [0], "1392418357": [0], "1392647501": [0], "1392921765": [0], "1393007536": [0], "1393111808": [0], "1393273224": [0], "1393470509": [0], "1393932504": [0], "1393965020": [0], "1394125488": [0], "1394164548": [0], "1394242273": [0], "1394426210": [0], "1394961181": [0], "1394994614": [0], "1395176843": [0], "1395248586": [0], "1395325254": [0], "1395506454": [0], "1395752408": [0], "1395757288": [0], "1395831257": [0], "1395967038": [0], "1395987861": [0], "1396607721": [0], "1396691047": [0], "1396714655": [0], "1397191074": [0], "1397714645": [0], "1397979769": [0], "1398184808": [0], "1398536215": [0], "1398556375": [0], "1399203382": [0], "1399293933": [0], "1399325166": [0], "1399901041": [0], "1399917545": [0], "1399989915": [0], "1400597564": [0], "1400856750": [0], "1401345010": [0], "1401569690": [0], "1401651367": [0], "1402227168": [0], "1402318583": [0], "1402355670": [0], "1402547127": [0], "1403581189": [0], "1403675753": [0], "1404042166": [0], "1404433655": [0], "1404577416": [0], "1404589190": [0], "1404633789": [0], "1405106949": [0], "1405163285": [0], "1405600386": [0], "1405764755": [0], "1405793634": [0], "1406286046": [0], "1406474021": [0, 1], "1406520288": [0], "1406880847": [0], "1406954806": [0], "1406985585": [0], "1407109085": [0], "1407213975": [0], "1407367300": [0], "1407593513": [0, 1], "1407699560": [0], "1407837984": [0], "1407987417": [0], "1408011662": [0], "1408074139": [0], "1409019210": [0], "1409427207": [0], "1409679155": [0], "1410056393": [0], "1410141496": [0], "1410364667": [0], "1411114656": [0], "1411285874": [0], "1411355448": [0], "1411539619": [0], "1411822238": [0], "1412070516": [0], "1412175651": [0], "1412501562": [0], "1412692644": [0], "1412863877": [0], "1413439164": [0], "1413753175": [0], "1413858043": [0], "1414220929": [0], "1414377947": [0], "1414495092": [0, 1], "1414692709": [0], "1414811931": [0], "1415067436": [0], "1415097501": [0], "1415574573": [0], "1415705470": [0], "1415715373": [0], "1416409879": [0], "1416422768": [0], "1416525059": [0], "1416665115": [0], "1416865308": [0], "1417000165": [0, 1], "1417038551": [0], "1417148787": [0, 1], "1417291826": [0], "1417333735": [0], "1417577147": [0], "1417787550": [0], "1417820715": [0], "1418608872": [0], "1418955933": [0], "1419052813": [0], "1419365937": [0], "1419718498": [0], "1419962031": [0], "1420004165": [0], "1420087101": [0], "1420483967": [0], "1420489538": [0], "1420616845": [0], "1420792951": [0], "1420969279": [0], "1420985591": [0], "1421257309": [0], "1421528408": [0], "1421605579": [0], "1421629897": [0], "1421758881": [0], "1421902404": [0], "1422508164": [0], "1422840316": [0], "1423109383": [0], "1423168024": [0, 1], "1423854201": [0], "1423943603": [0], "1424083503": [0], "1424084614": [0], "1424817147": [0], "1426078947": [0], "1426079395": [0], "1426131320": [0, 1], "1426355127": [0], "1426712678": [0], "1426948086": [0], "1427693573": [0], "1427841918": [0], "1427968298": [0], "1428781362": [0], "1428972588": [0], "1429054688": [0], "1429172122": [0], "1431002966": [0], "1431013615": [0], "1431180844": [0], "1431188471": [0], "1431242630": [0], "1431567364": [0], "1431724155": [0], "1431910232": [0], "1431993400": [0], "1432086181": [0, 1], "1432229503": [0, 1], "1432382131": [0], "1432414447": [0], "1432573751": [0], "1433593266": [0], "1434034828": [0], "1434215727": [0], "1434622493": [0], "1434859032": [0], "1435495208": [0], "1435922698": [0], "1436165350": [0], "1436195112": [0], "1436220035": [0], "1436259926": [0], "1436270977": [0], "1436504933": [0], "1436813081": [0], "1437151678": [0], "1437194267": [0, 1], "1437417578": [0], "1437425071": [0, 1], "1437644641": [0], "1438312945": [0], "1438355265": [0], "1438670524": [0], "1438728629": [0], "1439154810": [0], "1439217469": [0], "1439279075": [0], "1439355612": [0], "1439517925": [0], "1439727324": [0], "1440760475": [0, 1], "1440925134": [0], "1441177890": [0], "1441264847": [0], "1441324679": [0], "1441597590": [0], "1441675142": [0], "1441910443": [0], "1442878513": [0], "1443427506": [0, 1], "1443464998": [0], "1443474103": [0], "1443530650": [0], "1443808726": [0, 1], "1444030812": [0], "1444058206": [0], "1444148597": [0], "1444483294": [0], "1444494596": [0], "1444537475": [0], "1444883823": [0], "1445018939": [0], "1445158729": [0], "1445435678": [0], "1445534013": [0], "1445534241": [0], "1445848611": [0], "1445980843": [0], "1446332397": [0], "1446475157": [0], "1446874044": [0], "1447099831": [0], "1447154667": [0], "1447169355": [0], "1447176158": [0], "1447526975": [0], "1447598304": [0], "1447897845": [0], "1447949077": [0], "1448428458": [0], "1448817975": [0], "1449156900": [0], "1449210686": [0], "1449241909": [0], "1449516668": [0, 1], "1449523579": [0], "1449801545": [0], "1450021343": [0], "1450022441": [0], "1450025648": [0], "1450446242": [0], "1450726557": [0], "1450967467": [0], "1451350608": [0], "1451383233": [0, 1], "1451404890": [0], "1451408291": [0], "1451554630": [0], "1452201157": [0], "1452293994": [0], "1452306449": [0, 1], "1452359121": [0], "1452950108": [0, 1], "1453095460": [0], "1453179314": [0], "1453215000": [0], "1453573527": [0], "1453997879": [0], "1454979320": [0], "1455404883": [0, 1], "1455407422": [0], "1455581854": [0], "1456053662": [0], "1456230801": [0], "1456378053": [0], "1456594938": [0], "1456630525": [0], "1456635513": [0], "1456643830": [0], "1456853625": [0], "1456909015": [0], "1456935828": [0], "1457018177": [0], "1457147782": [0], "1457334403": [0], "1457587318": [0], "1457849096": [0], "1458175141": [0], "1458533545": [0], "1458860279": [0], "1458898899": [0], "1459265002": [0], "1459517794": [0], "1459690315": [0], "1460036000": [0], "1460054092": [0], "1460054141": [0], "1460101655": [0], "1460355683": [0], "1460424323": [0], "1460589085": [0], "1460921424": [0], "1461097819": [0], "1461705085": [0], "1462292056": [0], "1462350702": [0], "1462696571": [0], "1462806626": [0], "1463080377": [0], "1463916377": [0], "1463918198": [0], "1463948201": [0], "1464301488": [0], "1464367100": [0], "1464852931": [0], "1465374048": [0], "1465377878": [0], "1465645427": [0], "1465803187": [0], "1466156532": [0], "1466388694": [0], "1466513180": [0], "1466586462": [0], "1466985937": [0, 1], "1467353900": [0], "1467426965": [0], "1467566218": [0], "1467657253": [0], "1467886478": [0], "1468475321": [0], "1468843893": [0], "1469524669": [0], "1469525770": [0], "1469589961": [0], "1469848834": [0], "1470037873": [0], "1470120626": [0], "1470322066": [0], "1470322652": [0], "1470501299": [0], "1471017148": [0], "1471167432": [0], "1471261731": [0], "1471328176": [0], "1471425375": [0], "1471495420": [0], "1471509856": [0], "1471520960": [0], "1471844191": [0], "1471923688": [0], "1472474703": [0], "1472541008": [0], "1472776552": [0], "1473309481": [0], "1473334529": [0], "1473479454": [0], "1473591403": [0], "1473741959": [0], "1474410396": [0], "1474814741": [0], "1475881867": [0], "1475889545": [0], "1476139506": [0], "1476192995": [0], "1476256685": [0], "1476412944": [0], "1477280844": [0], "1477345314": [0], "1478324898": [0], "1478440712": [0], "1478588686": [0], "1478592989": [0], "1479104286": [0], "1479463955": [0, 1], "1479760326": [0], "1479968627": [0], "1480504014": [0], "1480571160": [0], "1480751694": [0], "1481553678": [0], "1481606337": [0], "1481827560": [0], "1481946246": [0], "1482223081": [0], "1482488401": [0], "1483580560": [0], "1483794414": [0], "1484007614": [0], "1484372063": [0, 1], "1484516387": [0], "1484703119": [0], "1485184344": [0], "1485222117": [0], "1485460843": [0], "1485554116": [0], "1486184540": [0], "1486185875": [0], "1486760309": [0], "1487401015": [0], "1487578456": [0], "1487664430": [0], "1487872592": [0], "1487902643": [0], "1487993129": [0], "1488563836": [0], "1488607677": [0], "1488754059": [0, 1], "1488843242": [0], "1488876116": [0], "1489161553": [0], "1489167763": [0], "1489168588": [0], "1489249837": [0], "1489615333": [0, 1], "1489722223": [0], "1489875437": [0], "1490400991": [0], "1490476328": [0], "1490548092": [0], "1490606726": [0], "1491998287": [0], "1492042134": [0], "1492193107": [0], "1492591429": [0], "1493358439": [0], "1493378362": [0], "1493774125": [0, 1], "1494011523": [0], "1494129632": [0], "1494209721": [0], "1494269079": [0, 1], "1494548709": [0], "1494562463": [0], "1495027469": [0], "1495521568": [0], "1495561082": [0], "1495741733": [0], "1495780438": [0], "1496000492": [0], "1496195819": [0, 1], "1496919352": [0], "1497704203": [0], "1497890181": [0], "1497913163": [0], "1497992696": [0], "1498988553": [0], "1499133485": [0], "1499348858": [0], "1499417916": [0], "1499554330": [0], "1499701160": [0], "1500139341": [0], "1500183460": [0, 1], "1501456775": [0], "1501644586": [0], "1501645784": [0], "1501996704": [0], "1502026549": [0], "1502300660": [0], "1502414573": [0], "1502623717": [0], "1503115738": [0], "1503349535": [0], "1503352414": [0], "1503374523": [0], "1503797930": [0], "1503946431": [0], "1504179872": [0], "1504750639": [0], "1505180794": [0], "1505383731": [0], "1505534085": [0], "1505675764": [0], "1505700506": [0], "1506077539": [0, 1], "1506157839": [0], "1506160354": [0], "1506212537": [0], "1506378475": [0], "1506428665": [0], "1506482230": [0], "1506498804": [0], "1506706849": [0], "1507133143": [0, 1], "1507188305": [0], "1507237075": [0], "1507257129": [0], "1507411301": [0], "1507476376": [0], "1507808215": [0], "1509593816": [0], "1509703385": [0], "1509813436": [0], "1510151140": [0], "1510187373": [0], "1510465391": [0], "1510610162": [0], "1510769431": [0], "1510834762": [0, 1], "1510898108": [0], "1510942834": [0], "1511199214": [0], "1511463367": [0], "1511582837": [0], "1511596026": [0], "1512160461": [0], "1512196777": [0], "1512438454": [0], "1512441875": [0], "1512483307": [0], "1513007964": [0], "1513197451": [0], "1513340341": [0], "1513983785": [0], "1514086568": [0, 1], "1514271627": [0], "1514548653": [0], "1515001361": [0], "1515191698": [0], "1515206400": [0], "1515276789": [0], "1515425981": [0], "1516009900": [0], "1516033112": [0], "1516045352": [0], "1516311189": [0], "1516372704": [0], "1516407242": [0], "1516849374": [0], "1516890318": [0], "1517144317": [0], "1517220492": [0], "1517245559": [0], "1517732126": [0], "1517961266": [0], "1518177564": [0], "1518181503": [0], "1518219135": [0], "1518232208": [0], "1518696877": [0], "1518735427": [0], "1518821395": [0, 1], "1519001015": [0], "1519060884": [0], "1519103556": [0], "1519221545": [0], "1519234736": [0], "1519540775": [0], "1520550867": [0], "1520919657": [0], "1520944263": [0], "1521057659": [0], "1521205470": [0], "1521440439": [0, 1], "1522178142": [0], "1522179221": [0], "1522195152": [0], "1522829100": [0], "1522876418": [0], "1522880454": [0], "1522882546": [0], "1522909762": [0], "1523132070": [0, 1], "1523247773": [0], "1523474781": [0], "1523781988": [0], "1524211565": [0], "1524240206": [0], "1524769591": [0], "1524876413": [0], "1525416225": [0], "1525582888": [0], "1525662895": [0], "1525697229": [0], "1525756157": [0], "1525963260": [0], "1526134242": [0, 1], "1526178287": [0], "1526272037": [0], "1526311087": [0], "1526730044": [0], "1526745623": [0], "1526793325": [0], "1527140752": [0], "1527375236": [0], "1527696585": [0], "1527881500": [0], "1528200386": [0], "1528443821": [0], "1528731420": [0], "1529338428": [0], "1529356685": [0], "1529862537": [0], "1530936754": [0], "1531119040": [0], "1531167424": [0], "1531177594": [0], "1531269804": [0], "1531912840": [0], "1532058524": [0, 1], "1532543260": [0, 1], "1532675201": [0], "1533065875": [0], "1533273791": [0], "1533344601": [0], "1533554482": [0, 1], "1534047536": [0], "1534055398": [0], "1534453938": [0], "1534639294": [0], "1534687023": [0], "1534846888": [0], "1535024507": [0], "1535283449": [0], "1535662487": [0], "1536078160": [0], "1536243007": [0], "1536390477": [0], "1536426232": [0], "1536634822": [0], "1536765309": [0], "1537240075": [0], "1537272969": [0], "1537408786": [0], "1537600270": [0, 1], "1537613187": [0, 1], "1537763900": [0], "1538011412": [0], "1538253741": [0], "1538697143": [0], "1538744944": [0], "1538958511": [0], "1539165146": [0], "1539293356": [0], "1539297990": [0], "1539387665": [0], "1539469761": [0], "1539719546": [0], "1539763817": [0], "1539950474": [0], "1539996128": [0], "1540130569": [0], "1540470495": [0], "1540581750": [0], "1540707897": [0], "1540865225": [0], "1541030978": [0], "1541346411": [0], "1541527470": [0], "1542146611": [0], "1542330910": [0], "1542339218": [0], "1542409231": [0], "1542460503": [0], "1542547552": [0], "1542558682": [0], "1542769316": [0], "1542802191": [0], "1542838132": [0], "1542908586": [0], "1543074914": [0], "1543387412": [0], "1543388050": [0], "1543629748": [0], "1544747792": [0, 1], "1544822957": [0], "1544835982": [0], "1545170420": [0], "1545326271": [0], "1545459306": [0], "1545487859": [0], "1545857279": [0], "1545980029": [0, 1], "1546220819": [0], "1546305573": [0], "1546391877": [0], "1546458893": [0], "1546522158": [0], "1546568150": [0], "1546715355": [0], "1547137898": [0], "1547219624": [0, 1], "1547767314": [0], "1547788660": [0], "1547826502": [0], "1548125400": [0], "1548561596": [0], "1548612262": [0], "1549218627": [0], "1549294072": [0], "1549477033": [0], "1549579701": [0], "1549633824": [0], "1549965511": [0], "1550234873": [0], "1550487679": [0], "1550497920": [0], "1550724519": [0], "1550738338": [0], "1550910638": [0], "1550939942": [0], "1551254401": [0], "1551575734": [0], "1551627694": [0], "1551883705": [0], "1552332688": [0], "1552446466": [0], "1553528594": [0], "1553584829": [0], "1553698015": [0], "1553764673": [0], "1554605554": [0], "1554700608": [0], "1555186994": [0], "1556089764": [0], "1556094963": [0], "1556185394": [0], "1556843311": [0], "1557034690": [0], "1557356280": [0], "1557385226": [0], "1557429225": [0], "1557532558": [0, 1], "1557790105": [0], "1557939516": [0, 1], "1558109051": [0], "1558204959": [0], "1558932070": [0, 1], "1559126508": [0], "1559301752": [0], "1559475599": [0], "1559735792": [0], "1559865853": [0, 1], "1560124024": [0, 1], "1560262981": [0], "1560438868": [0], "1560727252": [0], "1561578843": [0], "1561610362": [0], "1562024743": [0], "1562049440": [0], "1562122622": [0], "1562612767": [0], "1562692278": [0], "1562765783": [0], "1562992524": [0], "1563137908": [0], "1563747292": [0], "1563786401": [0], "1564406130": [0], "1564427592": [0], "1564584450": [0, 1], "1564842599": [0], "1564981711": [0], "1565725180": [0], "1566374565": [0], "1566535456": [0], "1566634479": [0], "1566772190": [0], "1566921678": [0], "1566938889": [0], "1567003733": [0], "1567086604": [0], "1567199620": [0], "1567428350": [0], "1567724715": [0], "1568069480": [0], "1568084827": [0], "1568132899": [0], "1568196654": [0], "1568242538": [0], "1568428219": [0], "1568580785": [0], "1568812908": [0], "1569069375": [0], "1569114937": [0, 1], "1569122239": [0], "1569137449": [0], "1569294742": [0], "1569364055": [0], "1569367466": [0], "1569786593": [0], "1569787270": [0], "1569904894": [0], "1569975459": [0], "1570578024": [0], "1570651310": [0], "1570804840": [0], "1571094845": [0], "1571217109": [0], "1571398393": [0], "1571426000": [0], "1571707723": [0], "1571752990": [0], "1572257005": [0], "1572347267": [0], "1572718095": [0], "1572825637": [0, 1], "1573116050": [0], "1573474354": [0], "1573593534": [0], "1573943334": [0], "1574124354": [0, 1], "1574340109": [0], "1574482868": [0], "1574635241": [0], "1574941480": [0], "1575180827": [0], "1575562948": [0], "1575633166": [0], "1575784338": [0], "1576342724": [0], "1576433278": [0], "1576468949": [0], "1576739365": [0], "1576820923": [0], "1576837936": [0], "1577494574": [0], "1577833516": [0], "1577894905": [0], "1578322293": [0], "1578635514": [0], "1578660093": [0], "1578682895": [0], "1578738874": [0], "1578768274": [0], "1578880081": [0], "1578952701": [0], "1579340503": [0], "1579376747": [0], "1579479361": [0], "1580039385": [0], "1580204395": [0], "1580216903": [0], "1580240766": [0], "1580364757": [0], "1580471005": [0], "1580503482": [0], "1580728054": [0], "1580960597": [0], "1581003453": [0], "1581067369": [0], "1581182521": [0], "1581227956": [0], "1581573162": [0], "1582038552": [0], "1582278802": [0], "1582289426": [0], "1582451156": [0], "1582523782": [0], "1582709623": [0], "1582830187": [0], "1582958992": [0], "1583142088": [0], "1583363841": [0], "1583592485": [0], "1583776000": [0], "1583800631": [0, 1], "1583808535": [0], "1584309970": [0], "1584350286": [0, 1], "1584553032": [0], "1584684307": [0], "1584889955": [0], "1585627295": [0], "1585761150": [0], "1586088299": [0], "1586237366": [0], "1586311627": [0], "1586365418": [0, 1], "1586619947": [0], "1587207463": [0], "1587224271": [0], "1587338140": [0, 1], "1587734313": [0], "1587880212": [0], "1588237935": [0], "1588277076": [0], "1588729288": [0], "1589207582": [0], "1589713279": [0], "1590237151": [0], "1590489688": [0], "1590693336": [0], "1590921683": [0], "1591562013": [0], "1591720914": [0], "1591955819": [0], "1592341989": [0], "1592403800": [0], "1592740731": [0], "1592910952": [0], "1592993013": [0], "1593080757": [0], "1593287769": [0], "1593471488": [0], "1593787430": [0], "1593856478": [0], "1593858046": [0], "1594384658": [0], "1594587956": [0], "1594622606": [0], "1594643960": [0], "1594756829": [0], "1594780751": [0], "1595012885": [0], "1595087871": [0], "1595905902": [0], "1596009073": [0], "1596108946": [0], "1596456989": [0], "1596518317": [0], "1596597145": [0, 1], "1596719329": [0, 1], "1596841303": [0], "1596870543": [0], "1596970674": [0], "1597506300": [0], "1597514861": [0], "1597730213": [0], "1597870481": [0], "1597930580": [0], "1598287092": [0], "1598349500": [0], "1598814606": [0], "1598993639": [0], "1599110345": [0], "1599144342": [0], "1599561823": [0], "1599615939": [0], "1599769963": [0], "1599787392": [0], "1599825390": [0], "1599901031": [0], "1600032837": [0], "1600471783": [0], "1600512656": [0], "1600532558": [0], "1601273982": [0], "1601423283": [0], "1601728271": [0], "1602003928": [0], "1602082003": [0], "1602548002": [0], "1603230007": [0], "1604011504": [0, 1], "1605015185": [0], "1605241084": [0], "1605474584": [0], "1605590241": [0], "1605643913": [0], "1605650247": [0], "1605977383": [0], "1605990461": [0], "1605993063": [0], "1606119864": [0], "1606348967": [0], "1606648311": [0], "1606933433": [0], "1607192566": [0, 1], "1607968663": [0], "1608252933": [0], "1608270169": [0], "1608354593": [0], "1608760010": [0, 1], "1608784841": [0], "1608964051": [0], "1608972852": [0], "1609134808": [0], "1609329981": [0, 1], "1609576730": [0], "1609796317": [0], "1610102124": [0], "1610372821": [0], "1610472402": [0], "1610783680": [0], "1610924805": [0], "1611202225": [0], "1611414471": [0, 1], "1611638409": [0], "1611851839": [0], "1612403575": [0], "1612683383": [0], "1612948838": [0], "1613154021": [0], "1613156654": [0], "1613542815": [0], "1613835181": [0], "1614571792": [0], "1614585450": [0], "1614610463": [0], "1614969659": [0], "1615008975": [0], "1615350500": [0], "1615433580": [0], "1615530696": [0], "1615694398": [0, 1], "1616120597": [0], "1616452278": [0], "1616479462": [0], "1616480280": [0], "1616519835": [0], "1616618046": [0], "1616649090": [0], "1616766718": [0], "1616972609": [0], "1617096660": [0], "1617111440": [0], "1617369998": [0], "1617374863": [0], "1617494640": [0], "1617506937": [0], "1617591619": [0], "1617757908": [0], "1617918152": [0], "1617963346": [0], "1617979203": [0], "1618062965": [0], "1618272176": [0], "1618738379": [0], "1618813313": [0], "1619267426": [0], "1619281020": [0], "1619438798": [0], "1620799224": [0], "1621471189": [0], "1622392243": [0], "1622760877": [0], "1622844933": [0, 1], "1622962377": [0], "1622962452": [0], "1623053817": [0], "1623087792": [0], "1623136315": [0], "1623877968": [0], "1623919427": [0], "1624236094": [0], "1624441276": [0], "1625640859": [0], "1625770912": [0], "1625974979": [0], "1626042347": [0], "1626197013": [0], "1626661735": [0], "1626960470": [0], "1627649797": [0], "1627995086": [0, 1], "1628357611": [0], "1628360181": [0], "1628603513": [0], "1628635445": [0], "1628748279": [0], "1628932686": [0], "1629276759": [0], "1629436687": [0], "1629797782": [0], "1630013393": [0], "1630991441": [0], "1631007967": [0], "1631596332": [0], "1632043984": [0], "1632071685": [0], "1632198661": [0], "1632257198": [0], "1632271101": [0], "1632474922": [0], "1632480735": [0], "1632488243": [0], "1632652052": [0], "1632956530": [0], "1633228833": [0], "1633427679": [0], "1633436892": [0], "1633612448": [0], "1633998042": [0], "1634194723": [0], "1634634324": [0], "1635124580": [0], "1635145886": [0], "1635205914": [0, 1], "1635384589": [0], "1635586999": [0], "1635800404": [0], "1635848327": [0], "1635985772": [0], "1636030207": [0], "1636203537": [0], "1636571388": [0], "1636579337": [0], "1636603901": [0, 1], "1636612660": [0], "1636677731": [0], "1636936782": [0], "1637016912": [0, 1], "1637024824": [0], "1637032277": [0], "1637062488": [0], "1637619765": [0], "1637771713": [0], "1638644157": [0], "1639124274": [0], "1639810266": [0], "1640048436": [0], "1640095752": [0], "1640159149": [0], "1640253378": [0, 1], "1640269333": [0], "1640313333": [0], "1640539763": [0], "1640581956": [0], "1640816465": [0], "1640916422": [0], "1640969116": [0], "1641054956": [0], "1641155606": [0], "1641182848": [0], "1641390972": [0], "1641578907": [0], "1641658955": [0], "1641757480": [0], "1641881177": [0], "1641897947": [0], "1642286282": [0], "1642302976": [0, 1], "1642545388": [0], "1643397908": [0], "1643935728": [0, 1], "1644346564": [0], "1644415336": [0], "1644621450": [0], "1644976119": [0], "1645251318": [0], "1645470411": [0, 1], "1646213900": [0], "1646287105": [0], "1646296701": [0], "1646323650": [0], "1646328731": [0], "1646560068": [0], "1646996033": [0], "1647420663": [0], "1648035341": [0], "1648467145": [0], "1648539287": [0], "1648728581": [0], "1649368657": [0], "1649809451": [0], "1650002700": [0], "1650069330": [0], "1650267810": [0], "1650413398": [0, 1], "1650710167": [0], "1651077498": [0], "1651287333": [0], "1651348507": [0], "1651438940": [0], "1652165383": [0], "1652379819": [0], "1652393760": [0], "1652837865": [0], "1653505430": [0], "1653642725": [0], "1653898623": [0], "1654067888": [0], "1654239565": [0], "1654280291": [0], "1654345025": [0], "1654910965": [0], "1654922831": [0], "1655036034": [0], "1655163327": [0], "1655220251": [0], "1655418608": [0, 1], "1655453481": [0], "1655840935": [0], "1655949550": [0], "1656053711": [0], "1656072681": [0], "1656224214": [0], "1656465818": [0], "1656642463": [0], "1656817001": [0], "1656933366": [0], "1657247298": [0], "1657332896": [0], "1657611818": [0, 1], "1657873070": [0], "1658214761": [0], "1658245849": [0], "1658505725": [0], "1658522194": [0], "1658528203": [0], "1658867623": [0], "1658941803": [0], "1658950775": [0], "1659106401": [0], "1659328462": [0], "1659801715": [0, 1], "1660165686": [0], "1660296130": [0], "1660589898": [0], "1660761125": [0], "1660961870": [0], "1661288016": [0], "1662106377": [0], "1662162198": [0], "1662697164": [0], "1662890252": [0], "1663030910": [0], "1663206178": [0], "1663209979": [0], "1663229925": [0], "1663231772": [0], "1663272805": [0], "1663488762": [0], "1663820689": [0], "1663866259": [0], "1663946193": [0], "1664033155": [0], "1664054639": [0], "1664065901": [0, 1], "1664147831": [0], "1664194915": [0], "1664276479": [0], "1664277338": [0], "1664331345": [0], "1664463933": [0], "1664505556": [0], "1664718301": [0], "1664801374": [0], "1664808290": [0], "1664829331": [0], "1664987004": [0], "1665190683": [0], "1665240758": [0], "1665269842": [0], "1665840831": [0, 1], "1666099072": [0], "1666204459": [0], "1666449325": [0], "1666630749": [0], "1666762742": [0], "1667329304": [0], "1667454522": [0], "1668040156": [0], "1668186430": [0], "1668821236": [0], "1668959019": [0], "1669055731": [0], "1669586553": [0], "1669836514": [0], "1669993050": [0], "1670484321": [0], "1670525054": [0], "1670813952": [0], "1670829024": [0], "1671501530": [0], "1671752914": [0], "1671788902": [0], "1672012437": [0], "1672030303": [0], "1672096183": [0], "1672116013": [0], "1672257460": [0], "1672416009": [0], "1672511191": [0], "1672916790": [0], "1673336550": [0], "1673526009": [0], "1673596654": [0], "1673830312": [0], "1674037817": [0], "1674071203": [0], "1674152692": [0], "1675138700": [0], "1675197253": [0], "1675229624": [0], "1675450795": [0], "1675622990": [0], "1675745141": [0], "1675862786": [0], "1675890948": [0], "1676225281": [0], "1676273826": [0], "1676282120": [0], "1676517097": [0], "1676774520": [0], "1676815649": [0], "1677327214": [0], "1677372673": [0], "1677528816": [0], "1677693460": [0], "1678015857": [0, 1], "1678478701": [0], "1678489310": [0], "1678810247": [0], "1678961358": [0], "1679013008": [0], "1679050399": [0], "1679074150": [0], "1679210352": [0], "1679489402": [0], "1679514526": [0], "1679528479": [0], "1680020425": [0], "1680259988": [0, 1], "1680378071": [0], "1680659485": [0], "1680698976": [0], "1680943683": [0], "1681233801": [0], "1681273088": [0], "1681466067": [0], "1681859809": [0], "1682071021": [0], "1682117693": [0, 1], "1682420592": [0], "1682520965": [0], "1682741657": [0], "1683215211": [0], "1683277410": [0], "1683367509": [0, 1], "1683585978": [0, 1], "1684122282": [0], "1684123010": [0], "1684374172": [0], "1684378675": [0], "1684646587": [0], "1684803438": [0], "1685318287": [0], "1685497823": [0], "1685520938": [0], "1686137134": [0], "1686222438": [0], "1686488545": [0], "1686518273": [0], "1686730885": [0], "1686976498": [0], "1687268889": [0], "1687381149": [0], "1687422079": [0], "1687648881": [0], "1687730983": [0, 1], "1688477838": [0], "1688631217": [0], "1688650486": [0, 1], "1688813532": [0], "1688962695": [0], "1689025166": [0, 1], "1689375621": [0], "1689491406": [0], "1689788130": [0], "1689941328": [0], "1690382155": [0], "1690821709": [0], "1691078146": [0], "1691481375": [0], "1691544728": [0], "1691629702": [0], "1692248795": [0], "1692624338": [0], "1692687182": [0], "1692735711": [0], "1692759442": [0, 1], "1693059473": [0], "1693064550": [0], "1693189577": [0], "1693332900": [0], "1693762207": [0], "1693819107": [0], "1694050162": [0], "1694592591": [0], "1694606033": [0], "1694767042": [0], "1694853076": [0], "1694929539": [0], "1695015789": [0], "1695210325": [0], "1695253681": [0], "1695501639": [0], "1695893116": [0], "1696008122": [0], "1696074296": [0], "1696439012": [0], "1696991399": [0], "1697202594": [0], "1697211090": [0], "1697324631": [0], "1697889100": [0], "1698062530": [0], "1699166023": [0], "1699552203": [0], "1699635309": [0], "1700031601": [0], "1700393261": [0], "1700540017": [0], "1700878032": [0], "1701065518": [0], "1701200648": [0, 1], "1701405727": [0], "1701560923": [0], "1701606553": [0], "1701902363": [0], "1701920727": [0], "1701997309": [0], "1702018576": [0, 1], "1702342632": [0], "1702352657": [0], "1702372588": [0], "1702849590": [0], "1702885552": [0], "1703424844": [0], "1704152984": [0], "1704176676": [0], "1704242582": [0], "1704816425": [0], "1705031860": [0], "1705508480": [0], "1705543850": [0], "1705816789": [0], "1705844512": [0], "1706081643": [0], "1706134855": [0], "1706304174": [0], "1707013987": [0], "1707059973": [0], "1707104400": [0], "1707426564": [0], "1707847236": [0], "1707977357": [0], "1708258559": [0], "1708447697": [0], "1708896700": [0], "1708934351": [0], "1709207408": [0], "1709393721": [0], "1709469488": [0], "1709587543": [0], "1710026127": [0], "1710192619": [0], "1710218042": [0], "1710776394": [0], "1710838695": [0], "1711036496": [0], "1711267562": [0], "1711299746": [0], "1711400498": [0], "1711425741": [0], "1712448303": [0], "1712775610": [0], "1712971214": [0], "1713187978": [0], "1713196424": [0], "1714134679": [0], "1714820329": [0], "1714855558": [0], "1715002771": [0], "1715084126": [0], "1715492238": [0], "1715609534": [0], "1715965612": [0], "1716024929": [0], "1716160272": [0], "1716293164": [0], "1716385849": [0], "1716553098": [0], "1716723506": [0], "1717282078": [0], "1717292778": [0, 1], "1717410944": [0], "1717487957": [0, 1], "1717632238": [0], "1718148569": [0], "1718181638": [0], "1718427311": [0], "1718498639": [0], "1718533342": [0], "1718650043": [0], "1718706444": [0], "1719045150": [0], "1719071905": [0], "1719185824": [0], "1719363303": [0], "1719634414": [0, 1], "1719797414": [0], "1719917309": [0], "1720300394": [0], "1720461353": [0], "1720510984": [0], "1720612324": [0], "1720893971": [0], "1721576278": [0], "1721722858": [0], "1721897928": [0], "1722668240": [0], "1723695761": [0], "1723757632": [0], "1723857900": [0], "1723862033": [0, 1], "1723945008": [0], "1723966189": [0], "1724653465": [0], "1724929376": [0], "1725039356": [0], "1725154496": [0], "1725174637": [0], "1725281221": [0], "1725308159": [0, 1], "1725972896": [0], "1726416641": [0], "1726687429": [0], "1726689998": [0], "1727024638": [0], "1727086552": [0], "1727543607": [0], "1727558497": [0], "1727622355": [0], "1728047531": [0], "1728754346": [0], "1728976946": [0], "1729044458": [0], "1729089308": [0], "1729432851": [0], "1729716576": [0], "1729816182": [0], "1729912953": [0], "1730154135": [0], "1730231863": [0], "1730442176": [0], "1731062324": [0], "1731422342": [0], "1731846800": [0], "1731905106": [0], "1732071077": [0], "1732277362": [0], "1732292031": [0], "1733577299": [0], "1733665306": [0], "1733667386": [0], "1733836452": [0], "1733896546": [0], "1734056220": [0, 1], "1734152403": [0], "1734396171": [0], "1734656638": [0], "1734679117": [0, 1], "1735319725": [0], "1735445928": [0, 1], "1735465139": [0], "1736092353": [0, 1], "1736094891": [0], "1736721858": [0], "1737214255": [0], "1737607648": [0], "1738103801": [0], "1738283938": [0], "1738380456": [0, 1], "1738428152": [0], "1738754131": [0], "1738877796": [0], "1738883940": [0], "1738931586": [0], "1739490913": [0], "1739784569": [0], "1739936483": [0], "1739949803": [0], "1740407519": [0], "1740449786": [0], "1740736893": [0], "1740819964": [0], "1741015395": [0], "1741250686": [0], "1741581878": [0], "1741600601": [0], "1741777657": [0], "1741889248": [0], "1742522231": [0], "1743458008": [0], "1743875565": [0], "1744174674": [0], "1744328030": [0, 1], "1744407893": [0], "1744444882": [0], "1744844625": [0], "1745245252": [0], "1745552291": [0], "1746064396": [0], "1746246022": [0], "1746325312": [0], "1746391501": [0], "1746441439": [0], "1746785285": [0], "1746880793": [0], "1746964527": [0], "1747056988": [0], "1747494645": [0], "1747514711": [0], "1747533492": [0], "1747681969": [0], "1747689393": [0], "1747790191": [0], "1747968734": [0], "1748084542": [0], "1748324833": [0], "1748332144": [0], "1748380748": [0], "1748662014": [0], "1749009204": [0, 1], "1749107546": [0], "1749735347": [0], "1750493903": [0], "1750977140": [0], "1751227376": [0], "1751611402": [0], "1751715236": [0], "1751717848": [0], "1752215989": [0], "1752221702": [0, 1], "1752222119": [0], "1752492766": [0], "1752514518": [0, 1], "1752944449": [0], "1752969900": [0], "1753542028": [0], "1753586272": [0], "1753655042": [0], "1753892160": [0], "1754714651": [0], "1755001166": [0], "1755159859": [0], "1755291228": [0], "1756062485": [0], "1756139114": [0], "1756364862": [0], "1756484997": [0], "1756656174": [0], "1756780424": [0], "1756917452": [0], "1757190879": [0], "1757256593": [0], "1757504492": [0], "1757667086": [0], "1758068194": [0], "1758346414": [0], "1758436275": [0], "1758554914": [0], "1758622124": [0], "1758755477": [0], "1759427058": [0], "1759862613": [0], "1759872001": [0], "1759946214": [0], "1760924659": [0], "1761008594": [0], "1761329078": [0], "1761363485": [0], "1761373862": [0], "1761456132": [0], "1761462155": [0], "1761541382": [0], "1761972787": [0], "1762581183": [0], "1762918674": [0], "1763086682": [0], "1763261983": [0], "1763452674": [0], "1763732533": [0], "1763913584": [0], "1764221330": [0], "1764484226": [0], "1764517032": [0], "1764524536": [0], "1765155405": [0], "1765359288": [0], "1766106313": [0], "1766925262": [0], "1766992792": [0], "1767274522": [0], "1768072088": [0], "1768506480": [0], "1769167116": [0], "1769827672": [0], "1769944236": [0], "1770275419": [0], "1770774028": [0], "1771041018": [0], "1771122372": [0], "1771285457": [0], "1771525575": [0], "1771758562": [0], "1772547794": [0], "1772907004": [0], "1772908696": [0], "1773088548": [0], "1773820048": [0], "1773919264": [0], "1774118949": [0], "1774713806": [0], "1774940882": [0], "1775598185": [0], "1775702373": [0], "1775876110": [0, 1], "1776223306": [0, 1], "1776629676": [0], "1776675330": [0], "1776815902": [0], "1776887447": [0], "1777079341": [0], "1777392053": [0], "1777762309": [0], "1778389685": [0], "1778662305": [0], "1779565450": [0], "1779593738": [0], "1780269879": [0], "1780274310": [0], "1780339039": [0], "1780341787": [0], "1780598704": [0], "1780947945": [0], "1781504891": [0, 1], "1781816067": [0], "1782286169": [0], "1782287323": [0], "1782369276": [0], "1782485357": [0], "1782818226": [0], "1783347582": [0], "1783414174": [0], "1783991019": [0], "1784405532": [0], "1785154000": [0], "1785253499": [0], "1785381581": [0], "1785764824": [0], "1786507847": [0], "1786902606": [0], "1786914149": [0], "1787123985": [0], "1787407239": [0], "1787467914": [0], "1787582684": [0], "1788104806": [0], "1788105487": [0], "1788512525": [0], "1788560986": [0], "1789019454": [0], "1789063885": [0], "1789378194": [0], "1789988809": [0], "1790094463": [0], "1790254054": [0, 1], "1790376634": [0], "1790764512": [0], "1791028545": [0], "1791553962": [0], "1791600275": [0], "1791823620": [0], "1791829972": [0], "1792570336": [0], "1792602966": [0], "1792720501": [0], "1792918250": [0], "1793142237": [0], "1793292474": [0], "1793752405": [0], "1793848687": [0], "1793929350": [0], "1794084601": [0], "1794292631": [0], "1794671340": [0], "1794806550": [0], "1795064249": [0], "1795209405": [0], "1795241251": [0], "1795310433": [0], "1795342334": [0], "1795342917": [0], "1795470189": [0], "1795521826": [0], "1795764300": [0], "1795795566": [0], "1795978428": [0], "1796352119": [0], "1796508811": [0], "1797069813": [0], "1797134985": [0], "1797463701": [0], "1797533334": [0], "1797816470": [0], "1797928883": [0], "1798531532": [0], "1798989414": [0], "1799141180": [0], "1799702576": [0], "1799829922": [0], "1799981543": [0], "1800043061": [0], "1800155205": [0], "1800328419": [0], "1800337381": [0], "1800417747": [0], "1800694675": [0], "1801082264": [0], "1801518843": [0], "1801864646": [0], "1801895083": [0], "1801932981": [0], "1803007960": [0], "1803041544": [0], "1803322128": [0], "1803440829": [0], "1803880133": [0], "1804179392": [0], "1804350645": [0], "1804367161": [0], "1804387177": [0], "1804453075": [0], "1804843537": [0], "1804935047": [0], "1805035661": [0], "1805137116": [0, 1], "1805247837": [0], "1805454425": [0], "1806163089": [0], "1806650573": [0], "1806729984": [0], "1806881740": [0], "1806940548": [0], "1807010711": [0], "1807111796": [0], "1807352586": [0], "1807527730": [0, 1], "1807881556": [0], "1807884635": [0], "1808309413": [0], "1808905109": [0], "1809175057": [0], "1809384803": [0], "1809720858": [0], "1809996364": [0], "1810174470": [0], "1810553381": [0], "1810704724": [0], "1810907373": [0], "1810909527": [0], "1810919765": [0], "1811270909": [0], "1811431578": [0], "1811444913": [0], "1811458639": [0], "1811526503": [0], "1811713713": [0], "1812327250": [0], "1812421503": [0], "1812498567": [0], "1812813179": [0], "1812907230": [0], "1812916096": [0], "1813196976": [0], "1813325265": [0], "1813517508": [0], "1813530941": [0], "1813596016": [0], "1814109099": [0], "1814404718": [0], "1815151199": [0], "1815504444": [0], "1816371493": [0], "1816546664": [0], "1816820313": [0], "1817310918": [0], "1817680667": [0], "1817712998": [0], "1817826785": [0], "1817847663": [0], "1817889863": [0, 1], "1818013177": [0], "1818025887": [0], "1818608309": [0], "1818857566": [0], "1819013194": [0], "1819166510": [0], "1819271614": [0], "1819505536": [0], "1819644653": [0], "1819734107": [0], "1819960385": [0], "1820163872": [0], "1820258263": [0], "1820535104": [0], "1820885767": [0], "1820910259": [0], "1820911993": [0], "1821311704": [0], "1821517114": [0], "1821519074": [0], "1821674275": [0], "1821999828": [0], "1822244292": [0, 1], "1822308216": [0], "1822453611": [0], "1822480210": [0], "1822914161": [0], "1823095573": [0], "1823099369": [0], "1823508446": [0, 1], "1823827811": [0], "1824548981": [0], "1824789124": [0], "1825202484": [0], "1825600443": [0], "1825737730": [0], "1825838578": [0], "1826085108": [0], "1826847066": [0], "1827328310": [0], "1827692941": [0], "1828357431": [0], "1828421908": [0], "1828517285": [0], "1828537450": [0], "1828826047": [0], "1829009037": [0], "1829469515": [0], "1829974411": [0], "1830017527": [0], "1830028343": [0], "1830044507": [0], "1830567868": [0], "1830779764": [0], "1831538515": [0, 1], "1831742559": [0], "1831752370": [0], "1831783465": [0], "1831832938": [0], "1831890625": [0], "1832017663": [0], "1832070276": [0], "1832573478": [0], "1832758468": [0], "1833674961": [0], "1833705541": [0], "1833855691": [0], "1834293370": [0], "1834940579": [0], "1834968638": [0], "1835376763": [0], "1835510995": [0], "1835723552": [0], "1836165127": [0], "1837472007": [0], "1837608946": [0], "1837625021": [0], "1837801619": [0], "1837951786": [0], "1837955864": [0, 1], "1838085783": [0], "1838387899": [0], "1838429854": [0], "1838702732": [0], "1838742232": [0], "1838860555": [0], "1839104169": [0], "1839195731": [0], "1839282940": [0], "1839819214": [0], "1839984914": [0], "1839992955": [0], "1839997013": [0], "1840123962": [0], "1840309478": [0], "1840523874": [0], "1841322378": [0], "1841582790": [0], "1842287335": [0], "1842843229": [0], "1843171728": [0, 1], "1843301262": [0], "1843468636": [0], "1843702122": [0, 1], "1843988727": [0], "1844227707": [0], "1844668995": [0], "1844975870": [0], "1845197377": [0], "1845312602": [0], "1845519498": [0], "1846582978": [0], "1846855164": [0], "1847438355": [0], "1847439803": [0, 1], "1847463735": [0], "1847568502": [0], "1848318570": [0, 1], "1848532968": [0], "1848632297": [0, 1], "1849021819": [0], "1849237128": [0], "1849687490": [0], "1849944329": [0], "1850132301": [0], "1850142791": [0], "1850278033": [0], "1850349411": [0], "1850380789": [0], "1850398788": [0], "1850543038": [0], "1850644940": [0], "1850819648": [0], "1850875364": [0], "1851041264": [0, 1], "1851152049": [0], "1851648624": [0], "1852136086": [0], "1852328210": [0], "1853283411": [0], "1853425525": [0], "1853652574": [0], "1853917983": [0], "1853972049": [0], "1853992742": [0, 1], "1854190642": [0, 1], "1854654733": [0], "1854678448": [0], "1854877152": [0], "1854925181": [0], "1855002542": [0], "1855167181": [0], "1855176766": [0], "1855232352": [0], "1855491287": [0], "1855712652": [0], "1855794768": [0], "1856082124": [0], "1856575365": [0], "1857131716": [0], "1857898516": [0], "1858890768": [0], "1859021291": [0], "1859206372": [0, 1], "1859206741": [0], "1859657734": [0], "1859669222": [0], "1859690364": [0], "1859728327": [0], "1859806846": [0], "1859922579": [0], "1860037674": [0], "1860245815": [0], "1861306418": [0], "1861344058": [0], "1861403447": [0], "1861423189": [0], "1861482702": [0], "1862126514": [0], "1862242142": [0], "1863100208": [0, 1], "1863126005": [0], "1863178568": [0], "1863541445": [0], "1864124312": [0], "1864353895": [0], "1864449678": [0], "1864696756": [0], "1864882929": [0], "1864944543": [0], "1865035567": [0], "1865263623": [0], "1865341132": [0], "1865440885": [0], "1865583316": [0], "1865872327": [0], "1865912543": [0], "1866046607": [0], "1866435621": [0], "1866470807": [0], "1866625012": [0], "1866915954": [0], "1866981435": [0], "1867035889": [0], "1867820805": [0], "1867982776": [0], "1868107502": [0], "1868141654": [0], "1868216273": [0], "1868271203": [0], "1868542116": [0], "1868660434": [0], "1869050273": [0], "1869560055": [0], "1869616840": [0], "1870174594": [0], "1870376576": [0], "1870577358": [0], "1870971436": [0, 1], "1871318309": [0], "1871708927": [0], "1871712305": [0], "1871865630": [0], "1872111504": [0], "1872170813": [0], "1872249309": [0], "1872460618": [0], "1872872117": [0], "1872967668": [0], "1873663668": [0], "1873946173": [0], "1874438205": [0], "1874572710": [0], "1874639624": [0], "1875117790": [0], "1875922975": [0], "1875977923": [0], "1875987277": [0], "1876430383": [0], "1876474801": [0], "1876651253": [0], "1876935347": [0], "1877074863": [0], "1877098703": [0], "1877242042": [0], "1877312067": [0], "1877820210": [0], "1878026601": [0], "1878223995": [0], "1878245214": [0], "1878308377": [0], "1878585353": [0], "1878704819": [0], "1878862982": [0], "1879312155": [0], "1879810994": [0], "1880329660": [0], "1880417657": [0], "1880441026": [0], "1880833647": [0], "1881200661": [0], "1881391850": [0], "1881434811": [0], "1881574209": [0], "1882301749": [0], "1882337187": [0], "1882381534": [0], "1882477972": [0], "1882782519": [0], "1883605821": [0], "1884434297": [0], "1884718414": [0], "1885124285": [0], "1885192374": [0], "1885417446": [0], "1885472453": [0], "1885595326": [0], "1885984120": [0], "1886141639": [0], "1886296990": [0], "1886757366": [0], "1887216626": [0], "1887578819": [0], "1887581632": [0, 1], "1887917765": [0], "1888181600": [0], "1888300566": [0], "1888508900": [0], "1888971846": [0], "1889174249": [0], "1889268029": [0], "1889799142": [0], "1889859633": [0], "1889883079": [0], "1890735720": [0], "1890766544": [0], "1890794296": [0], "1891018075": [0], "1891162542": [0], "1891528212": [0, 1], "1891567027": [0], "1891961644": [0], "1892438257": [0], "1892488303": [0], "1892826633": [0], "1893569704": [0, 1], "1893888919": [0], "1894050667": [0], "1894283431": [0], "1894528806": [0], "1895261370": [0, 1], "1895410982": [0, 1], "1895496134": [0], "1895652587": [0], "1896335746": [0], "1896636172": [0], "1896872122": [0], "1897467280": [0], "1897879071": [0], "1898114220": [0], "1898552980": [0], "1898579158": [0], "1898586169": [0], "1898630004": [0], "1898833115": [0], "1899084445": [0, 1], "1899623481": [0], "1899815354": [0], "1899912111": [0], "1900475245": [0], "1900892501": [0], "1900930139": [0], "1901032412": [0], "1901303421": [0], "1901527505": [0], "1901630498": [0], "1901738978": [0], "1902190333": [0], "1902720787": [0], "1902728284": [0], "1902787166": [0], "1902928937": [0], "1902935782": [0], "1903031574": [0], "1903126083": [0], "1903602114": [0], "1904284092": [0], "1904294639": [0], "1904575064": [0], "1904673914": [0], "1904704993": [0], "1905962527": [0], "1906211793": [0], "1906632457": [0], "1906978760": [0], "1906980620": [0], "1907199950": [0], "1907200634": [0], "1907244129": [0], "1907600403": [0], "1907795862": [0], "1908015626": [0], "1908193722": [0], "1909100410": [0], "1909195317": [0], "1909250327": [0], "1909747727": [0], "1909952618": [0], "1910285002": [0], "1910304721": [0], "1910333205": [0], "1910465528": [0], "1910555294": [0], "1910973663": [0], "1911349704": [0], "1911620076": [0], "1911782154": [0], "1911809207": [0], "1911824416": [0], "1911882295": [0], "1912054953": [0], "1912366861": [0, 1], "1912689877": [0], "1913045721": [0], "1913155315": [0], "1913319150": [0], "1913730260": [0, 1], "1913774240": [0], "1914054115": [0], "1914111678": [0], "1914230784": [0], "1914251269": [0], "1914582472": [0], "1914705874": [0, 1], "1914903866": [0], "1914933188": [0], "1914989948": [0], "1914994275": [0], "1915156405": [0], "1915237506": [0], "1915245524": [0], "1915460847": [0], "1915536875": [0], "1915812771": [0], "1915872359": [0], "1916368719": [0], "1916766608": [0], "1916917030": [0], "1916986654": [0], "1916993320": [0], "1917187392": [0], "1917241679": [0], "1918212942": [0], "1918234601": [0], "1918661481": [0], "1918692373": [0], "1919433217": [0], "1919454382": [0], "1919548555": [0], "1919651107": [0], "1919683955": [0], "1919874619": [0], "1919942355": [0], "1920112893": [0], "1921860149": [0], "1921919409": [0], "1922197508": [0], "1922337264": [0], "1922432785": [0], "1922462020": [0], "1922486003": [0], "1922685054": [0], "1922702547": [0], "1922776493": [0], "1922898053": [0], "1923669925": [0], "1923685616": [0, 1], "1924261266": [0], "1924503410": [0], "1924719260": [0], "1925027867": [0], "1925175461": [0], "1925362126": [0], "1925461092": [0], "1925776797": [0], "1926096432": [0], "1926297786": [0], "1926536126": [0], "1926750247": [0], "1926836960": [0], "1926927540": [0], "1927031036": [0], "1927250571": [0], "1927748266": [0], "1927759195": [0], "1927949588": [0], "1927979453": [0], "1928001994": [0], "1928022212": [0], "1928153079": [0], "1928281944": [0, 1], "1928397055": [0], "1928459036": [0], "1928532386": [0], "1928907692": [0], "1929005214": [0], "1929339784": [0], "1929536304": [0], "1929568432": [0], "1929601724": [0], "1929753719": [0], "1929993366": [0], "1929997566": [0], "1930090313": [0], "1930330549": [0], "1930487638": [0], "1930504339": [0, 1], "1930527196": [0], "1931136621": [0], "1931165009": [0], "1931275981": [0], "1931539028": [0], "1931775705": [0], "1932150062": [0], "1932186329": [0], "1932243754": [0], "1932304419": [0], "1933155398": [0], "1933198062": [0], "1933471230": [0], "1934055585": [0], "1934253270": [0], "1934271684": [0], "1934410674": [0], "1934520286": [0], "1934555179": [0], "1935303856": [0], "1935423507": [0], "1935483025": [0], "1935512145": [0], "1935902953": [0], "1936352948": [0], "1936378211": [0], "1936804069": [0], "1936819527": [0], "1937102501": [0], "1937445760": [0], "1937691241": [0], "1938129385": [0], "1938493091": [0], "1938588210": [0], "1939290041": [0], "1939387001": [0], "1939636725": [0], "1940223523": [0], "1940348811": [0], "1940636857": [0], "1940813777": [0], "1941109529": [0], "1941355843": [0], "1941700070": [0], "1941785233": [0], "1941897443": [0], "1941929528": [0], "1942005672": [0], "1942042506": [0], "1942260317": [0], "1942446318": [0], "1942538685": [0], "1942590466": [0], "1942663631": [0], "1942775928": [0], "1943206779": [0], "1943688278": [0], "1943854877": [0], "1943942534": [0], "1944093926": [0], "1945271552": [0], "1945724801": [0], "1945966701": [0], "1946054285": [0], "1946104206": [0], "1946780217": [0], "1947180804": [0], "1947350735": [0], "1947515506": [0, 1], "1947715197": [0], "1948105551": [0], "1948376807": [0], "1949177264": [0], "1949192711": [0], "1949218838": [0], "1949963862": [0], "1950100333": [0], "1950128808": [0], "1950337974": [0], "1950543678": [0], "1950744916": [0], "1950847330": [0], "1950889758": [0], "1951453743": [0], "1951465527": [0], "1951769754": [0], "1951780490": [0], "1951821601": [0], "1951976634": [0], "1952186043": [0], "1952617025": [0], "1952880030": [0], "1953087407": [0, 1], "1953220136": [0], "1953436805": [0], "1953943712": [0], "1954016934": [0], "1954143470": [0], "1954265648": [0], "1954314780": [0], "1954749599": [0], "1955077113": [0], "1955154854": [0], "1955225227": [0], "1955593895": [0], "1955697808": [0], "1955867187": [0, 1], "1956167950": [0], "1956518673": [0], "1956664602": [0], "1957139536": [0], "1957355332": [0], "1957496090": [0], "1957986272": [0], "1958200263": [0], "1958479481": [0], "1958753419": [0], "1958773967": [0], "1958841703": [0], "1959185600": [0], "1959264281": [0], "1959272730": [0], "1959389261": [0], "1959620392": [0], "1959642628": [0], "1960164799": [0], "1960523633": [0], "1960546400": [0], "1960929070": [0], "1961173593": [0], "1961522515": [0], "1962033851": [0], "1963024207": [0], "1963281557": [0], "1963658469": [0], "1964047256": [0], "1964588242": [0], "1964599615": [0], "1964635412": [0], "1964657937": [0], "1964678798": [0], "1964911747": [0], "1965013480": [0], "1965063600": [0], "1965372806": [0], "1965395131": [0], "1965905128": [0], "1965918053": [0], "1965957531": [0], "1965996807": [0, 1], "1966504882": [0], "1966574246": [0], "1966575848": [0, 1], "1966754340": [0], "1966894640": [0], "1967423479": [0], "1967739955": [0], "1968137307": [0], "1968225664": [0], "1968315795": [0], "1968695515": [0], "1968813565": [0], "1969460068": [0], "1969896422": [0], "1970115987": [0], "1970309118": [0], "1970637412": [0], "1970692238": [0], "1970718898": [0], "1971132001": [0], "1971263072": [0, 1], "1971285168": [0], "1971386675": [0], "1971487857": [0], "1973726340": [0], "1974007109": [0], "1974033734": [0], "1974389172": [0], "1974509991": [0], "1974929919": [0], "1975198310": [0], "1975453729": [0], "1975686691": [0], "1975894087": [0], "1975914322": [0], "1975937519": [0], "1975966684": [0], "1976128348": [0], "1976375567": [0], "1976418187": [0], "1976466985": [0], "1977308476": [0], "1977380022": [0], "1977381543": [0], "1977478004": [0], "1977509999": [0], "1977778551": [0], "1977792022": [0], "1977879257": [0], "1978175315": [0], "1978237481": [0], "1978244204": [0], "1978444864": [0], "1978904010": [0], "1979571963": [0], "1980370344": [0], "1980385128": [0], "1980761147": [0], "1980836161": [0], "1981018246": [0], "1981337596": [0], "1981362630": [0], "1981675377": [0], "1981835365": [0], "1981929223": [0], "1982077123": [0], "1982287769": [0], "1982416755": [0], "1982493659": [0], "1982634101": [0], "1983313086": [0], "1983450664": [0], "1983450722": [0], "1983548392": [0, 1], "1983599829": [0], "1983624170": [0], "1983663765": [0, 1], "1983957320": [0], "1984192526": [0], "1984333139": [0], "1984595736": [0], "1984665109": [0], "1984814373": [0], "1984981103": [0], "1985189834": [0], "1985226118": [0], "1985361181": [0], "1985541610": [0, 1], "1985571268": [0], "1985660064": [0], "1986261938": [0], "1986747452": [0], "1987893247": [0], "1988450755": [0], "1988474563": [0], "1988547292": [0], "1988681178": [0], "1988858604": [0], "1988884670": [0], "1989523366": [0], "1989584217": [0], "1989776207": [0], "1989934258": [0], "1989961124": [0], "1989964245": [0], "1990080130": [0], "1990504505": [0, 1], "1990747346": [0], "1990816950": [0, 1], "1990817656": [0], "1990886590": [0], "1991545231": [0], "1991658284": [0], "1992349074": [0], "1992393091": [0], "1992441153": [0], "1992875926": [0], "1992922276": [0], "1992949780": [0], "1992988993": [0], "1993144375": [0], "1993160581": [0], "1993179009": [0], "1993227214": [0], "1993325089": [0, 1], "1993375916": [0], "1993940032": [0, 1], "1994404926": [0], "1994410714": [0], "1994502388": [0], "1994599955": [0], "1995251538": [0], "1995461061": [0], "1995667619": [0], "1995821613": [0], "1995904304": [0], "1996213354": [0], "1996237236": [0], "1996729484": [0], "1996751117": [0], "1997224880": [0], "1997235258": [0], "1997259769": [0], "1998141596": [0], "1998240664": [0], "2000576311": [0], "2000640580": [0], "2000652457": [0], "2000988163": [0], "2001579561": [0], "2002210197": [0], "2002360025": [0], "2002418308": [0], "2002449571": [0], "2002908389": [0], "2003148519": [0], "2003153009": [0], "2003311601": [0, 1], "2003810889": [0], "2004183165": [0], "2004205759": [0], "2004374726": [0], "2004425345": [0], "2004433749": [0], "2004538240": [0], "2005567515": [0], "2005615309": [0], "2005951057": [0], "2005953552": [0], "2006404240": [0], "2006503656": [0], "2006782764": [0], "2006904322": [0], "2007335111": [0], "2007573143": [0], "2007852226": [0], "2008225828": [0], "2008341690": [0], "2008416631": [0], "2008543548": [0], "2008588633": [0], "2008811486": [0], "2009077907": [0, 1], "2009223686": [0], "2009385770": [0], "2009412606": [0], "2009494285": [0], "2009872684": [0], "2010181875": [0], "2010341827": [0], "2010342892": [0], "2010441741": [0], "2011098234": [0], "2011120006": [0], "2011133604": [0], "2011167724": [0], "2011491125": [0], "2012272721": [0, 1], "2012392467": [0], "2012412128": [0, 1], "2012509857": [0], "2012966526": [0, 1], "2013164502": [0], "2013678152": [0], "2013748354": [0], "2013760456": [0], "2014135111": [0], "2014455371": [0], "2014529003": [0], "2015153169": [0], "2015322067": [0], "2015591304": [0], "2015701126": [0], "2015738422": [0], "2015863800": [0], "2016141421": [0], "2016169118": [0], "2016894532": [0], "2016904661": [0], "2017018294": [0, 1], "2017059839": [0], "2017364102": [0], "2017591414": [0], "2017611169": [0], "2017688721": [0], "2018170871": [0], "2018353722": [0], "2018481965": [0], "2018807986": [0], "2019330954": [0], "2019621292": [0], "2020449260": [0], "2020489512": [0], "2020659448": [0], "2020811612": [0], "2020882106": [0], "2021434882": [0], "2021469891": [0], "2021674170": [0], "2021716496": [0], "2021762279": [0], "2022023559": [0], "2022170127": [0], "2022334829": [0], "2022342141": [0], "2022346401": [0], "2022366468": [0], "2022462997": [0], "2022624225": [0], "2022653887": [0, 1], "2023094867": [0], "2023463802": [0], "2023498630": [0], "2023735770": [0], "2024118348": [0], "2024710752": [0], "2025050667": [0], "2025489818": [0], "2025542794": [0], "2026263838": [0], "2026555237": [0], "2027155651": [0], "2027255768": [0], "2027612802": [0], "2028209854": [0], "2028220218": [0, 1], "2028465934": [0], "2028555449": [0], "2028794217": [0], "2028853686": [0], "2028884263": [0], "2029164013": [0], "2029198776": [0], "2030104832": [0], "2030109735": [0], "2030367676": [0], "2030605424": [0], "2030673708": [0], "2030893891": [0], "2030935105": [0], "2030975326": [0], "2031014845": [0], "2031159827": [0], "2031197764": [0], "2031732058": [0], "2031846085": [0], "2031884889": [0], "2032033636": [0], "2032115185": [0], "2032161661": [0], "2032597674": [0], "2032945709": [0], "2033279647": [0], "2033339834": [0], "2033879503": [0, 1], "2034022808": [0], "2034187078": [0], "2034188062": [0], "2034202412": [0], "2034203709": [0], "2034282053": [0], "2034288231": [0], "2034420916": [0], "2034899507": [0], "2035398439": [0], "2035546101": [0], "2035701971": [0], "2035731851": [0], "2036099989": [0], "2036243799": [0], "2036957242": [0], "2037202633": [0, 1], "2037747666": [0], "2038230627": [0], "2038441823": [0], "2038461568": [0], "2038950002": [0], "2038968275": [0], "2039033861": [0], "2039195676": [0], "2039292003": [0], "2039412625": [0], "2039547685": [0], "2039713262": [0], "2039835282": [0], "2040267390": [0], "2040282775": [0], "2040383713": [0], "2040583491": [0], "2040903938": [0], "2040951645": [0], "2041155066": [0], "2041495932": [0], "2041529046": [0], "2041586300": [0], "2041751399": [0], "2042513143": [0], "2042565820": [0], "2042621845": [0], "2042962050": [0], "2043137160": [0], "2043472906": [0], "2043658528": [0], "2043775369": [0], "2043785761": [0], "2044109311": [0], "2044227628": [0], "2044390270": [0], "2044827486": [0], "2045226373": [0], "2045273640": [0], "2045511235": [0], "2045764556": [0], "2046004150": [0, 1], "2046212554": [0], "2046477182": [0], "2046513282": [0], "2046802090": [0], "2047502827": [0], "2047862358": [0], "2048256140": [0], "2048671024": [0], "2048695455": [0], "2048742662": [0], "2049178586": [0], "2049554619": [0], "2049599882": [0], "2050017307": [0], "2050024633": [0], "2050162535": [0], "2050203756": [0], "2050956437": [0], "2051290887": [0], "2051633994": [0], "2052020264": [0], "2052031772": [0], "2052184537": [0], "2052430011": [0], "2052438990": [0], "2052561765": [0], "2052807698": [0], "2053458268": [0], "2053777987": [0], "2054066272": [0], "2054070983": [0], "2054071579": [0], "2054163756": [0], "2054253766": [0], "2054734903": [0], "2054986850": [0], "2055094741": [0], "2055219898": [0], "2055376323": [0], "2055490850": [0], "2055656305": [0, 1], "2055662659": [0], "2055809946": [0], "2056197587": [0], "2056565578": [0], "2056695963": [0], "2056810648": [0], "2056824218": [0], "2057804457": [0], "2057936724": [0], "2058075225": [0], "2058292154": [0], "2058893857": [0], "2059319442": [0], "2059621621": [0], "2059792873": [0], "2060245937": [0], "2060594747": [0], "2060878905": [0], "2061176946": [0], "2061692068": [0], "2061969639": [0], "2062050568": [0], "2062090335": [0], "2062280024": [0], "2062289397": [0], "2062375736": [0], "2062415334": [0, 1], "2062659708": [0], "2062739107": [0], "2063021390": [0], "2063040889": [0], "2063443904": [0], "2063565527": [0], "2063600825": [0], "2064143499": [0, 1], "2064285316": [0], "2064450173": [0], "2064899230": [0], "2065302654": [0], "2065391218": [0], "2065842117": [0], "2065862272": [0], "2065986467": [0], "2066122166": [0], "2066169643": [0], "2066208355": [0], "2066456374": [0], "2066639392": [0], "2066978239": [0], "2067734010": [0], "2067955613": [0], "2068070850": [0], "2068139291": [0], "2068251365": [0], "2068526934": [0], "2069047798": [0], "2069432180": [0], "2069684356": [0], "2069774092": [0], "2069899728": [0], "2070035738": [0], "2070307717": [0], "2070862432": [0], "2071005950": [0], "2071872677": [0], "2071906770": [0], "2071929350": [0], "2071979040": [0], "2072086947": [0], "2072116577": [0], "2072152475": [0], "2072344611": [0], "2072545487": [0], "2072612912": [0], "2072621666": [0], "2072872778": [0], "2073021279": [0], "2073044895": [0], "2073136744": [0], "2073416593": [0], "2073497367": [0], "2073695864": [0], "2073725809": [0], "2073934018": [0], "2074080576": [0], "2074163552": [0], "2074239336": [0], "2074275655": [0], "2074356459": [0], "2074578977": [0], "2074697128": [0], "2074864992": [0], "2074882092": [0], "2075125272": [0], "2075532051": [0], "2075657247": [0], "2075861484": [0], "2076583843": [0], "2076647486": [0], "2076672198": [0], "2076760966": [0], "2077182517": [0], "2077858333": [0], "2078358672": [0], "2078530927": [0], "2078899207": [0], "2079196604": [0], "2079221146": [0], "2079415511": [0], "2079437377": [0], "2079577635": [0], "2079796021": [0, 1], "2079910665": [0], "2080822238": [0], "2080827119": [0], "2080942386": [0], "2081314051": [0], "2081571294": [0], "2081719610": [0], "2081763420": [0], "2081933235": [0], "2082466116": [0], "2082525092": [0], "2082725360": [0], "2083302132": [0], "2083326496": [0], "2083373059": [0], "2083473209": [0], "2083626551": [0], "2083632734": [0], "2083752202": [0], "2083811866": [0], "2083917474": [0], "2084629699": [0], "2084670092": [0], "2084749768": [0, 1], "2084999229": [0], "2085083966": [0], "2085097274": [0], "2085165014": [0], "2085215024": [0], "2085641399": [0], "2085928209": [0], "2086291519": [0], "2086368224": [0, 1], "2086591542": [0], "2086706112": [0, 1], "2086734052": [0], "2087106688": [0], "2088300578": [0], "2088384498": [0], "2088658236": [0], "2088818189": [0], "2088949640": [0], "2089161021": [0], "2089303283": [0], "2089326714": [0], "2089376020": [0], "2089414517": [0], "2089444009": [0], "2089597487": [0, 1], "2089753463": [0], "2089855986": [0], "2089964008": [0], "2090089020": [0], "2090210030": [0], "2090317611": [0], "2090549934": [0, 1], "2090654508": [0], "2090978819": [0], "2091018987": [0], "2091711980": [0], "2092086939": [0], "2092206474": [0], "2092326719": [0], "2093313706": [0], "2093421316": [0], "2094049794": [0], "2094245165": [0], "2094332170": [0], "2094848516": [0], "2094969066": [0], "2095106825": [0], "2095139478": [0], "2095141672": [0], "2095862940": [0], "2095938871": [0], "2096109787": [0], "2096241408": [0], "2096359021": [0], "2096378837": [0], "2096404641": [0], "2096491061": [0], "2096505967": [0], "2096543098": [0], "2096941444": [0], "2097343707": [0], "2097798760": [0], "2097907328": [0], "2097918448": [0], "2098024905": [0], "2098979881": [0], "2099595443": [0], "2099784578": [0, 1], "2100041804": [0], "2100231020": [0], "2100512369": [0], "2100532600": [0], "2100646040": [0, 1], "2100910742": [0], "2100990133": [0], "2102117544": [0], "2102433870": [0], "2102552588": [0], "2102814959": [0], "2103004617": [0], "2103345539": [0], "2103434945": [0], "2103745298": [0], "2103973306": [0], "2104008474": [0], "2104095032": [0], "2104383983": [0], "2105065272": [0], "2105290129": [0], "2105363496": [0], "2106160078": [0], "2106656433": [0], "2106788989": [0], "2106810382": [0], "2106856803": [0], "2107013891": [0], "2107109471": [0], "2107416910": [0], "2107472364": [0], "2107517042": [0], "2107522414": [0], "2107564948": [0], "2107621900": [0], "2107841760": [0], "2108315102": [0], "2108456400": [0], "2108829040": [0], "2108830769": [0], "2109388045": [0], "2109402179": [0], "2109547045": [0], "2109747393": [0], "2110163135": [0], "2110190317": [0], "2110286199": [0], "2110411883": [0, 1], "2110694573": [0], "2110706753": [0], "2110825687": [0], "2111457038": [0], "2111816391": [0, 1], "2112157052": [0], "2112265233": [0], "2112519680": [0], "2112557677": [0, 1], "2112843985": [0], "2113049936": [0], "2113267667": [0], "2113276908": [0], "2113599514": [0], "2113848229": [0], "2114030427": [0], "2114238829": [0], "2114312485": [0], "2114334002": [0], "2115028399": [0], "2115107662": [0], "2115531380": [0], "2115900840": [0], "2115968501": [0], "2116172231": [0], "2116272060": [0], "2116748855": [0], "2116808803": [0], "2116901537": [0], "2117124371": [0], "2117565791": [0, 1], "2117715258": [0], "2117785843": [0], "2118235503": [0, 1], "2118291107": [0], "2118320014": [0], "2118579495": [0], "2118649620": [0], "2118734099": [0], "2118774081": [0, 1], "2119628576": [0], "2120354059": [0], "2121009473": [0], "2121227868": [0], "2121277037": [0], "2121356320": [0], "2121551751": [0], "2121683906": [0], "2122361229": [0], "2122841775": [0], "2122944552": [0], "2122991223": [0], "2123271985": [0], "2123325808": [0], "2123375083": [0], "2123511810": [0], "2123523754": [0], "2123562770": [0, 1], "2124174879": [0], "2124277427": [0], "2124355491": [0], "2124687739": [0], "2124709362": [0], "2124995711": [0], "2125103016": [0], "2125639138": [0, 1], "2125640335": [0], "2125745239": [0], "2125808557": [0], "2125836107": [0], "2126324214": [0], "2126540602": [0], "2126601173": [0], "2127271667": [0], "2127458170": [0], "2127537146": [0], "2127554534": [0], "2127682859": [0], "2127871525": [0, 1], "2127942910": [0], "2127960253": [0], "2128192928": [0], "2128490534": [0], "2128501706": [0], "2128560661": [0], "2128694425": [0], "2129182600": [0], "2129787619": [0], "2130448955": [0], "2130494392": [0], "2130742419": [0], "2130798060": [0], "2130868226": [0], "2130892706": [0], "2131080780": [0], "2131239827": [0], "2131567732": [0, 1], "2131917169": [0], "2132540681": [0], "2132599753": [0], "2132876998": [0], "2132894179": [0], "2133060353": [0], "2133343566": [0], "2133404423": [0], "2133781199": [0, 1], "2134057576": [0], "2136328094": [0], "2136387096": [0], "2136684259": [0], "2137601794": [0], "2137640377": [0], "2137803641": [0], "2138154394": [0], "2138165549": [0], "2138448229": [0], "2138674848": [0], "2138962718": [0], "2139228842": [0], "2139618164": [0], "2139719691": [0], "2139734849": [0], "2140044661": [0], "2140122631": [0], "2140127395": [0, 1], "2140195581": [0], "2140691244": [0], "2140945810": [0], "2141214889": [0], "2141402934": [0], "2142176214": [0], "2142246180": [0], "2143037834": [0], "2143350021": [0], "2144019816": [0], "2144130013": [0], "2144247689": [0], "2144303596": [0], "2144537917": [0], "2144827463": [0, 1], "2145092867": [0, 1], "2145483743": [0], "2145533679": [0], "2145565430": [0], "2146222159": [0], "2146309399": [0], "2146380130": [0], "2146427829": [0], "2146550030": [0], "524640": [1], "996218": [1], "1677842": [1], "1943998": [1], "3027412": [1], "3565461": [1], "3850568": [1], "4033372": [1], "4128345": [1], "4481770": [1], "4805405": [1], "4991922": [1], "6301343": [1], "6339869": [1], "6478552": [1], "6902783": [1], "7350539": [1], "7403604": [1], "7728335": [1], "8402924": [1], "8860051": [1], "9101513": [1], "9217586": [1], "10948809": [1], "11017359": [1], "11058696": [1], "11464875": [1], "11500707": [1], "11755451": [1], "11765822": [1], "12075305": [1], "12103558": [1], "12122884": [1], "12123911": [1], "12313842": [1], "12432614": [1], "12931139": [1], "13441988": [1], "13626808": [1], "13926380": [1], "14055738": [1], "14084151": [1], "14233234": [1], "14592715": [1], "14649085": [1], "14742061": [1], "15218296": [1], "15400279": [1], "15715082": [1], "15789754": [1], "16047683": [1], "16421779": [1], "16673083": [1], "16711337": [1], "16751005": [1], "16845050": [1], "17081455": [1], "17300968": [1], "17339520": [1], "18209280": [1], "19134435": [1], "19297214": [1], "19494947": [1], "19641766": [1], "19756685": [1], "19867382": [1], "20894079": [1], "21107855": [1], "21177166": [1], "21381403": [1], "21651971": [1], "22046674": [1], "22350713": [1], "22549848": [1], "22593972": [1], "22747702": [1], "22964921": [1], "23269854": [1], "23762388": [1], "23853536": [1], "23882570": [1], "23990657": [1], "24169867": [1], "24285356": [1], "24303168": [1], "24321305": [1], "24400603": [1], "24636535": [1], "24818259": [1], "25005444": [1], "25226667": [1], "25638559": [1], "25643018": [1], "25858165": [1], "25932677": [1], "26127384": [1], "26250253": [1], "26364449": [1], "26499458": [1], "26735315": [1], "26974569": [1], "27091987": [1], "27429828": [1], "27566876": [1], "27807937": [1], "27974541": [1], "28281612": [1], "28319516": [1], "28630550": [1], "28650193": [1], "29394916": [1], "29509629": [1], "29774794": [1], "30157518": [1], "30211074": [1], "30638615": [1], "31077595": [1], "31391351": [1], "31572034": [1], "31918606": [1], "32032417": [1], "32248197": [1], "32523852": [1], "32980767": [1], "33232182": [1], "33428624": [1], "33541813": [1], "33583915": [1], "33755852": [1], "33814442": [1], "33992259": [1], "34298949": [1], "34459400": [1], "34909801": [1], "35125618": [1], "35204842": [1], "35823695": [1], "36768877": [1], "36998508": [1], "37037013": [1], "37304271": [1], "37306378": [1], "37422884": [1], "38432995": [1], "39296924": [1], "39633027": [1], "39719173": [1], "39844544": [1], "39953751": [1], "40206210": [1], "40565199": [1], "40740207": [1], "40750498": [1], "41083317": [1], "41088996": [1], "41587768": [1], "41902424": [1], "42150835": [1], "42236383": [1], "42484406": [1], "43099731": [1], "43269188": [1], "43378637": [1], "43985559": [1], "44047580": [1], "44284800": [1], "44316282": [1], "44499252": [1], "44850406": [1], "44874045": [1], "44876824": [1], "44986468": [1], "45075628": [1], "45130444": [1], "45131062": [1], "45307429": [1], "45353726": [1], "46075997": [1], "46328102": [1], "46350985": [1], "46479223": [1], "46503958": [1], "46880778": [1], "46908272": [1], "46932683": [1], "47284164": [1], "47502801": [1], "47535268": [1], "47835476": [1], "47927801": [1], "48393968": [1], "48547630": [1], "48640973": [1], "48711734": [1], "48774667": [1], "49187145": [1], "49862485": [1], "49916719": [1], "49990733": [1], "50507897": [1], "50813047": [1], "50989667": [1], "51245217": [1], "51554860": [1], "51629074": [1], "51744787": [1], "51870507": [1], "51965288": [1], "52575667": [1], "52659948": [1], "53204357": [1], "53487930": [1], "53741859": [1], "53802920": [1], "53860101": [1], "53895004": [1], "53928001": [1], "54569338": [1], "54665705": [1], "56357561": [1], "56622931": [1], "56659614": [1], "56816463": [1], "56910563": [1], "57258375": [1], "57564955": [1], "57753774": [1], "57921730": [1], "58156994": [1], "58220810": [1], "58534111": [1], "59387405": [1], "59585087": [1], "60128224": [1], "60654481": [1], "60945385": [1], "61015924": [1], "61361389": [1], "61481901": [1], "61796956": [1], "62023795": [1], "62108788": [1], "62174914": [1], "62638875": [1], "62730156": [1], "62733350": [1], "62791255": [1], "63047090": [1], "63195455": [1], "63235848": [1], "63269527": [1], "63357552": [1], "63671393": [1], "63706807": [1], "63926445": [1], "64618870": [1], "64863971": [1], "64952357": [1], "65048151": [1], "65154680": [1], "65193237": [1], "65349449": [1], "65370257": [1], "65488984": [1], "65612297": [1], "65646554": [1], "65669782": [1], "66158015": [1], "66713330": [1], "66936833": [1], "66966055": [1], "67020999": [1], "67152431": [1], "67370260": [1], "67594153": [1], "67718709": [1], "67962498": [1], "68254181": [1], "68527850": [1], "68953293": [1], "69027804": [1], "69238325": [1], "69930831": [1], "70142818": [1], "70266607": [1], "70298545": [1], "70519418": [1], "71018599": [1], "71060708": [1], "71065366": [1], "71111670": [1], "71465982": [1], "71608945": [1], "71802616": [1], "71931409": [1], "72171849": [1], "72200206": [1], "72311677": [1], "72326671": [1], "72331498": [1], "72416194": [1], "72426010": [1], "72519902": [1], "72793088": [1], "72860527": [1], "73548031": [1], "73751261": [1], "73881513": [1], "74060279": [1], "74367788": [1], "74372876": [1], "75387065": [1], "75491488": [1], "75546677": [1], "76085861": [1], "76188436": [1], "76211682": [1], "76452092": [1], "76612607": [1], "76740599": [1], "76780720": [1], "76822113": [1], "77012621": [1], "77403704": [1], "77404126": [1], "77534269": [1], "77622805": [1], "77822563": [1], "78589458": [1], "78633528": [1], "78910462": [1], "79371609": [1], "79396058": [1], "79449452": [1], "80183091": [1], "80321010": [1], "80373955": [1], "80539542": [1], "80750560": [1], "80785591": [1], "80857151": [1], "80878357": [1], "80947196": [1], "81293778": [1], "81463166": [1], "81714734": [1], "81730850": [1], "81894297": [1], "82174310": [1], "82680491": [1], "82797516": [1], "82881281": [1], "83189522": [1], "83456424": [1], "83849169": [1], "84070997": [1], "84229878": [1], "84295082": [1], "84336424": [1], "84694858": [1], "85578678": [1], "85734091": [1], "85893151": [1], "85975241": [1], "86450291": [1], "86475872": [1], "86575796": [1], "86681859": [1], "86818654": [1], "86865849": [1], "87163429": [1], "87175780": [1], "87260714": [1], "87550624": [1], "87737476": [1], "88374743": [1], "88558941": [1], "88727996": [1], "88838910": [1], "88869138": [1], "88958218": [1], "88958694": [1], "89023214": [1], "89055546": [1], "89221330": [1], "89422659": [1], "89769367": [1], "90038731": [1], "90103076": [1], "90242882": [1], "90376339": [1], "90505787": [1], "90554534": [1], "90724786": [1], "90913394": [1], "91146651": [1], "91231940": [1], "91350859": [1], "91467872": [1], "92020432": [1], "92087290": [1], "92279786": [1], "92701107": [1], "92826610": [1], "92867565": [1], "93169210": [1], "93202968": [1], "93568565": [1], "93620330": [1], "93697297": [1], "93870926": [1], "94083287": [1], "94165790": [1], "94341281": [1], "94516502": [1], "95090057": [1], "95126155": [1], "95230715": [1], "95284255": [1], "95940420": [1], "96491841": [1], "96614464": [1], "96627662": [1], "96701304": [1], "96749938": [1], "96870691": [1], "97697378": [1], "97859212": [1], "98256212": [1], "98455369": [1], "98484849": [1], "98765157": [1], "98795473": [1], "99087678": [1], "99091654": [1], "99365599": [1], "99494025": [1], "99629560": [1], "99808022": [1], "99934113": [1], "100245579": [1], "100546345": [1], "100703648": [1], "101106505": [1], "101142706": [1], "101674354": [1], "101725964": [1], "101944885": [1], "102246924": [1], "102457140": [1], "102541202": [1], "102740422": [1], "102875472": [1], "103109208": [1], "103367736": [1], "103467554": [1], "103679803": [1], "104066784": [1], "104377532": [1], "104787081": [1], "105098594": [1], "105105922": [1], "105397485": [1], "105934112": [1], "106138203": [1], "106787451": [1], "107493831": [1], "107501253": [1], "107886820": [1], "108030953": [1], "108071545": [1], "108163114": [1], "108499191": [1], "108542076": [1], "108645436": [1], "108807650": [1], "108984881": [1], "109153681": [1], "109163072": [1], "109442237": [1], "109692074": [1], "109747766": [1], "110650055": [1], "110713510": [1], "111066331": [1], "111168373": [1], "111735202": [1], "112127025": [1], "112423866": [1], "112454056": [1], "112662903": [1], "112718965": [1], "112740397": [1], "113572121": [1], "114155027": [1], "114197737": [1], "114238650": [1], "114352463": [1], "114511490": [1], "115149116": [1], "115406239": [1], "115933236": [1], "116066063": [1], "116293291": [1], "116325982": [1], "116460548": [1], "116778298": [1], "116798167": [1], "116872125": [1], "117045881": [1], "117593331": [1], "117728873": [1], "117832806": [1], "117993649": [1], "118267196": [1], "118356909": [1], "118517868": [1], "118569202": [1], "118589372": [1], "118589900": [1], "119499722": [1], "119505512": [1], "119760097": [1], "119987898": [1], "120098502": [1], "120105998": [1], "121066584": [1], "121422284": [1], "121700965": [1], "121776807": [1], "122395496": [1], "122562763": [1], "122790275": [1], "123346968": [1], "123441439": [1], "123551498": [1], "123691501": [1], "124137375": [1], "124469137": [1], "124625050": [1], "124768472": [1], "124920966": [1], "125145381": [1], "126130167": [1], "126667378": [1], "126733749": [1], "126741981": [1], "127107432": [1], "127303182": [1], "127329375": [1], "127790355": [1], "127980392": [1], "128213661": [1], "128245773": [1], "128370568": [1], "128989572": [1], "129003149": [1], "129051083": [1], "129113311": [1], "129299370": [1], "129337965": [1], "129378946": [1], "129954524": [1], "130644999": [1], "131073177": [1], "131173654": [1], "131199624": [1], "131290494": [1], "131362655": [1], "131570039": [1], "131874120": [1], "131951616": [1], "132062509": [1], "132864295": [1], "132992451": [1], "134405093": [1], "134443241": [1], "134577459": [1], "135017050": [1], "135599690": [1], "135738009": [1], "135847168": [1], "136483247": [1], "136717915": [1], "137343260": [1], "137353924": [1], "137399361": [1], "137506903": [1], "137711637": [1], "137745268": [1], "137816615": [1], "138021123": [1], "138373891": [1], "138426872": [1], "138909193": [1], "138954967": [1], "138976178": [1], "139167924": [1], "139462593": [1], "140500710": [1], "140632612": [1], "141041347": [1], "141049217": [1], "141063129": [1], "141148933": [1], "141667057": [1], "142148975": [1], "142158561": [1], "142192246": [1], "142231013": [1], "142807393": [1], "142813305": [1], "142878623": [1], "142957187": [1], "143162809": [1], "143569300": [1], "143693936": [1], "143790108": [1], "143945412": [1], "144088044": [1], "145373434": [1], "145418621": [1], "145483061": [1], "145972293": [1], "146107443": [1], "146274757": [1], "146828142": [1], "147233913": [1], "147296594": [1], "147605926": [1], "148062832": [1], "148388485": [1], "148568057": [1], "149309726": [1], "149850992": [1], "149854242": [1], "149994677": [1], "150322630": [1], "150715990": [1], "150781196": [1], "151202457": [1], "151420425": [1], "151705184": [1], "151807224": [1], "152563315": [1], "153064360": [1], "153405644": [1], "153451905": [1], "153458537": [1], "153639800": [1], "153754110": [1], "153963636": [1], "154050579": [1], "154366668": [1], "154526417": [1], "154872477": [1], "155283250": [1], "155888235": [1], "155899106": [1], "156011863": [1], "156240646": [1], "156279037": [1], "156310892": [1], "156322606": [1], "156355508": [1], "156595286": [1], "156603644": [1], "156851979": [1], "156864675": [1], "156867741": [1], "157064131": [1], "157070477": [1], "157408022": [1], "157588143": [1], "157696876": [1], "157779076": [1], "158087681": [1], "158131787": [1], "158306485": [1], "158452351": [1], "158629484": [1], "158654977": [1], "159866857": [1], "160376291": [1], "160619034": [1], "161074675": [1], "161945270": [1], "162495264": [1], "162519527": [1], "162536002": [1], "162605310": [1], "162839209": [1], "163249116": [1], "163664941": [1], "163940407": [1], "164112589": [1], "164552760": [1], "164576916": [1], "164974914": [1], "165115272": [1], "165577179": [1], "165733290": [1], "166203327": [1], "166216113": [1], "167610738": [1], "167924766": [1], "168063177": [1], "168158314": [1], "168436800": [1], "168524706": [1], "168910774": [1], "169038311": [1], "169113175": [1], "169494919": [1], "169694195": [1], "169813092": [1], "170003054": [1], "170064470": [1], "170227426": [1], "170335025": [1], "170406610": [1], "170625858": [1], "170825085": [1], "170927372": [1], "170988222": [1], "171127939": [1], "171279228": [1], "171498761": [1], "171517933": [1], "171544720": [1], "172086709": [1], "172424983": [1], "172508564": [1], "172788176": [1], "172816795": [1], "172847787": [1], "172868777": [1], "172975274": [1], "173118023": [1], "173125661": [1], "173229865": [1], "173295065": [1], "173401168": [1], "173450953": [1], "173831123": [1], "174278026": [1], "174366691": [1], "174731139": [1], "175044127": [1], "175340824": [1], "175938616": [1], "176192581": [1], "176269879": [1], "176782082": [1], "177003269": [1], "177092215": [1], "177150857": [1], "177287209": [1], "177446431": [1], "177628029": [1], "177929419": [1], "178348087": [1], "178469796": [1], "178579397": [1], "178789173": [1], "179257502": [1], "179414239": [1], "179861593": [1], "179894617": [1], "180138875": [1], "180314875": [1], "180454448": [1], "180500228": [1], "180545118": [1], "181027065": [1], "181250916": [1], "181660213": [1], "182363839": [1], "182414149": [1], "182529801": [1], "182836301": [1], "182944548": [1], "183012645": [1], "183349942": [1], "183451271": [1], "183768416": [1], "184008283": [1], "184339009": [1], "184416016": [1], "184656023": [1], "185317318": [1], "185342234": [1], "186046473": [1], "186218965": [1], "186547249": [1], "186666164": [1], "186898508": [1], "187065357": [1], "187071369": [1], "187292415": [1], "187439470": [1], "187552167": [1], "188484257": [1], "188494757": [1], "188854632": [1], "189330893": [1], "189861577": [1], "190266381": [1], "190669326": [1], "190870471": [1], "190961512": [1], "191205792": [1], "191767079": [1], "191888495": [1], "192395955": [1], "192777855": [1], "193071273": [1], "193329936": [1], "193507726": [1], "193551880": [1], "194208489": [1], "194682787": [1], "194686018": [1], "194790008": [1], "195310772": [1], "195718288": [1], "195735370": [1], "195793886": [1], "196607055": [1], "196665288": [1], "196728641": [1], "196790001": [1], "197680476": [1], "197951573": [1], "198015628": [1], "198276844": [1], "198806776": [1], "198921126": [1], "199175501": [1], "199393641": [1], "199526099": [1], "199601550": [1], "200420070": [1], "200637050": [1], "200855279": [1], "201435363": [1], "201733398": [1], "201796692": [1], "202016422": [1], "202230198": [1], "202391916": [1], "202826727": [1], "202931228": [1], "203002884": [1], "203072301": [1], "203222109": [1], "203508252": [1], "203508660": [1], "203545068": [1], "204244482": [1], "204405432": [1], "204472383": [1], "204829731": [1], "204860667": [1], "204902746": [1], "205440407": [1], "205606700": [1], "205629065": [1], "206081214": [1], "206119706": [1], "206301005": [1], "206501268": [1], "206608539": [1], "206754366": [1], "206922674": [1], "207131757": [1], "207801164": [1], "207860724": [1], "207888531": [1], "207900843": [1], "207972857": [1], "208368715": [1], "208480180": [1], "209657949": [1], "209691003": [1], "209784326": [1], "209801537": [1], "209914928": [1], "210071384": [1], "210216799": [1], "210245489": [1], "210639112": [1], "211017938": [1], "211078060": [1], "211315599": [1], "211448493": [1], "211663498": [1], "212272667": [1], "212487244": [1], "212500773": [1], "212631640": [1], "212746152": [1], "212922412": [1], "212944563": [1], "212968122": [1], "213062940": [1], "213414587": [1], "213604050": [1], "213904040": [1], "213917665": [1], "214039445": [1], "214310325": [1], "215628053": [1], "215989271": [1], "216296956": [1], "216590684": [1], "216663399": [1], "216917664": [1], "216949710": [1], "217187377": [1], "217381054": [1], "217660264": [1], "217899215": [1], "217974417": [1], "218117234": [1], "218213936": [1], "218265119": [1], "218417171": [1], "218481049": [1], "218574236": [1], "218665123": [1], "218967735": [1], "219100641": [1], "219140170": [1], "219514256": [1], "220101705": [1], "220245542": [1], "220412978": [1], "220496396": [1], "220517525": [1], "221096628": [1], "221542603": [1], "222304177": [1], "222620414": [1], "222635336": [1], "222665051": [1], "222805140": [1], "223058451": [1], "223110147": [1], "223350029": [1], "223484965": [1], "223538461": [1], "223676230": [1], "223785891": [1], "225436838": [1], "225458077": [1], "225521161": [1], "225688608": [1], "227103977": [1], "227621779": [1], "227902694": [1], "227935106": [1], "228284706": [1], "228297419": [1], "228514483": [1], "229156795": [1], "229345787": [1], "229471465": [1], "229594240": [1], "230000596": [1], "230134754": [1], "230188601": [1], "230233532": [1], "230496069": [1], "231143353": [1], "231933203": [1], "231986748": [1], "232234020": [1], "232548901": [1], "232695746": [1], "232700774": [1], "232904692": [1], "233896966": [1], "233977754": [1], "234039939": [1], "234632811": [1], "234747452": [1], "234882194": [1], "234937584": [1], "235652718": [1], "235906065": [1], "236015399": [1], "236049114": [1], "236104433": [1], "236279529": [1], "236753035": [1], "236931591": [1], "237089065": [1], "238045179": [1], "238118575": [1], "238354651": [1], "239090623": [1], "239165540": [1], "239895579": [1], "240013985": [1], "240228220": [1], "240601318": [1], "241070279": [1], "241305359": [1], "241721744": [1], "241728634": [1], "241928146": [1], "241953889": [1], "241972238": [1], "242118293": [1], "242182856": [1], "242333716": [1], "242527850": [1], "242911814": [1], "243081327": [1], "243118619": [1], "243120535": [1], "243289061": [1], "243669151": [1], "243680800": [1], "243988407": [1], "244120449": [1], "244233992": [1], "244729829": [1], "244817948": [1], "245039345": [1], "245228168": [1], "245547199": [1], "245861874": [1], "245878006": [1], "246314826": [1], "246335247": [1], "246616359": [1], "246807357": [1], "247172574": [1], "247546861": [1], "247552017": [1], "247618421": [1], "247916145": [1], "248301216": [1], "248370659": [1], "248985874": [1], "249013642": [1], "249063634": [1], "249808977": [1], "249842327": [1], "249997038": [1], "250286606": [1], "250434860": [1], "250490373": [1], "250989225": [1], "250999007": [1], "251139615": [1], "251286569": [1], "251449786": [1], "251555924": [1], "251592639": [1], "251923461": [1], "251944711": [1], "252234245": [1], "252806115": [1], "253298755": [1], "253401378": [1], "253603080": [1], "254011692": [1], "254263251": [1], "254553139": [1], "254972660": [1], "255224880": [1], "255368848": [1], "255645084": [1], "255672865": [1], "255713620": [1], "255738875": [1], "255989006": [1], "256010385": [1], "256348989": [1], "256529878": [1], "256554642": [1], "256816497": [1], "257183843": [1], "257881871": [1], "257994202": [1], "258065389": [1], "258807551": [1], "258843104": [1], "259018494": [1], "259533525": [1], "260013147": [1], "260249068": [1], "260360974": [1], "261738064": [1], "262115378": [1], "262161622": [1], "262625684": [1], "262929617": [1], "263546989": [1], "263800848": [1], "263884416": [1], "264014158": [1], "264207465": [1], "264313940": [1], "264577230": [1], "264601988": [1], "264783471": [1], "265122574": [1], "265858552": [1], "266043887": [1], "266174168": [1], "266421410": [1], "266586283": [1], "266957001": [1], "267243658": [1], "267357845": [1], "267882653": [1], "268062283": [1], "268207732": [1], "268545652": [1], "269041266": [1], "269095595": [1], "269155791": [1], "269413869": [1], "269441467": [1], "269585196": [1], "269599321": [1], "269670973": [1], "269759797": [1], "269832659": [1], "270127480": [1], "270487180": [1], "270798084": [1], "270959235": [1], "271142002": [1], "271144637": [1], "271235051": [1], "271301562": [1], "271323161": [1], "271368116": [1], "271706550": [1], "271797441": [1], "271999159": [1], "272081323": [1], "272934779": [1], "273124630": [1], "273585150": [1], "273832793": [1], "273923567": [1], "274129572": [1], "274186104": [1], "274383675": [1], "274419508": [1], "274499571": [1], "274762281": [1], "274907964": [1], "275027590": [1], "275185763": [1], "275478811": [1], "275502433": [1], "275779907": [1], "276029102": [1], "276213260": [1], "276279079": [1], "276746291": [1], "277116486": [1], "277199187": [1], "277311452": [1], "277465086": [1], "278087193": [1], "278121242": [1], "278223510": [1], "278255335": [1], "278413517": [1], "278605586": [1], "278867154": [1], "279501259": [1], "279519867": [1], "280049986": [1], "280272281": [1], "281869850": [1], "281917084": [1], "281963936": [1], "282268705": [1], "282923003": [1], "283505736": [1], "283958365": [1], "284105323": [1], "284398056": [1], "285091997": [1], "285280214": [1], "285360868": [1], "285504777": [1], "286455114": [1], "286636036": [1], "286989457": [1], "287205254": [1], "287480454": [1], "288270795": [1], "288551633": [1], "288674872": [1], "288950335": [1], "289079577": [1], "289194973": [1], "289326707": [1], "289331151": [1], "289446035": [1], "289590411": [1], "289652385": [1], "290018705": [1], "290033579": [1], "290055554": [1], "290118506": [1], "290193250": [1], "290293451": [1], "290548753": [1], "290620641": [1], "290786302": [1], "291251829": [1], "291517340": [1], "291591552": [1], "291654987": [1], "291734677": [1], "291990421": [1], "292222140": [1], "292493553": [1], "292548062": [1], "292768894": [1], "293395082": [1], "293462408": [1], "293606582": [1], "293780428": [1], "293790156": [1], "294261371": [1], "294539017": [1], "294722616": [1], "294738283": [1], "294825865": [1], "294886770": [1], "295083250": [1], "295290793": [1], "295315475": [1], "295448739": [1], "295557860": [1], "295591314": [1], "295685456": [1], "295953074": [1], "296090477": [1], "296149954": [1], "296210657": [1], "296293212": [1], "296709897": [1], "297050766": [1], "297225301": [1], "297232504": [1], "297331406": [1], "297682674": [1], "297704400": [1], "297794314": [1], "297892347": [1], "298537200": [1], "298648862": [1], "298668924": [1], "298940869": [1], "299044210": [1], "299202615": [1], "299226047": [1], "299529033": [1], "299614084": [1], "299617200": [1], "299844130": [1], "299868799": [1], "300313329": [1], "300468922": [1], "301804455": [1], "302045285": [1], "302279500": [1], "302845256": [1], "303242740": [1], "303364712": [1], "303383261": [1], "303450515": [1], "304089621": [1], "304295736": [1], "304767749": [1], "304869107": [1], "304961229": [1], "304969560": [1], "305027341": [1], "305029662": [1], "305075906": [1], "305226207": [1], "305329786": [1], "305557768": [1], "306033625": [1], "306081518": [1], "306103116": [1], "306171097": [1], "306339465": [1], "306358517": [1], "307013948": [1], "307393388": [1], "307421688": [1], "307472558": [1], "307492605": [1], "307568491": [1], "307762602": [1], "308068660": [1], "308299850": [1], "308490957": [1], "308637644": [1], "308877667": [1], "309165834": [1], "309245702": [1], "309931200": [1], "310124877": [1], "310296694": [1], "310647475": [1], "310677849": [1], "310728786": [1], "311047621": [1], "311199952": [1], "311625565": [1], "311975207": [1], "312014195": [1], "312258981": [1], "312340912": [1], "312938210": [1], "312939247": [1], "312981591": [1], "313159114": [1], "313328914": [1], "313659058": [1], "313875732": [1], "313954623": [1], "314194223": [1], "314451462": [1], "314715111": [1], "314786208": [1], "314958704": [1], "315068125": [1], "315226530": [1], "315332038": [1], "315408836": [1], "315513029": [1], "315631486": [1], "315722284": [1], "316320561": [1], "316353644": [1], "316367885": [1], "316479907": [1], "316488296": [1], "316632863": [1], "316750500": [1], "316813236": [1], "317144332": [1], "317177995": [1], "317534334": [1], "317851713": [1], "318028641": [1], "318030721": [1], "318126200": [1], "318712291": [1], "318842228": [1], "319265866": [1], "319606044": [1], "319641270": [1], "319831217": [1], "319913817": [1], "320256724": [1], "320502650": [1], "320729879": [1], "320830387": [1], "320923755": [1], "321462178": [1], "321542761": [1], "321619187": [1], "321728818": [1], "322325692": [1], "322924665": [1], "322990108": [1], "323029286": [1], "323138524": [1], "323335512": [1], "323425743": [1], "323573662": [1], "323709185": [1], "323823939": [1], "323934876": [1], "324454908": [1], "324740867": [1], "324807765": [1], "325058611": [1], "325387698": [1], "325517943": [1], "325579746": [1], "325913391": [1], "325916483": [1], "325983601": [1], "326191017": [1], "326440628": [1], "326790136": [1], "327251688": [1], "327325624": [1], "327380354": [1], "328035450": [1], "328111344": [1], "328134990": [1], "328376968": [1], "328416338": [1], "328505460": [1], "328643142": [1], "328709416": [1], "329077294": [1], "329300643": [1], "329605894": [1], "329625861": [1], "330165185": [1], "330314991": [1], "330386231": [1], "330914814": [1], "331217657": [1], "331265090": [1], "331327654": [1], "331451351": [1], "332565308": [1], "332598559": [1], "333078855": [1], "333819950": [1], "333834410": [1], "333992477": [1], "334038581": [1], "334282115": [1], "334415951": [1], "334429908": [1], "334844297": [1], "335003375": [1], "335058169": [1], "335551679": [1], "335735288": [1], "335751920": [1], "335819812": [1], "336179429": [1], "336519609": [1], "336575618": [1], "336884782": [1], "337267750": [1], "337414414": [1], "337702649": [1], "338123543": [1], "338327300": [1], "338367888": [1], "338610181": [1], "339032283": [1], "339293991": [1], "339305404": [1], "339595166": [1], "339749162": [1], "340273229": [1], "340542617": [1], "340817756": [1], "340893662": [1], "341162351": [1], "341194423": [1], "341203528": [1], "341533260": [1], "341615637": [1], "341617000": [1], "341626922": [1], "341737545": [1], "342125914": [1], "342355187": [1], "343015075": [1], "343233853": [1], "343405636": [1], "343835622": [1], "343843852": [1], "343962700": [1], "344264090": [1], "344858692": [1], "344922198": [1], "345007981": [1], "345479003": [1], "345481252": [1], "345579228": [1], "345803783": [1], "346079979": [1], "346108855": [1], "346465218": [1], "347248450": [1], "347414750": [1], "347593878": [1], "347720409": [1], "348024920": [1], "348392754": [1], "348603813": [1], "348676333": [1], "348699604": [1], "348935189": [1], "348937319": [1], "349069045": [1], "349190028": [1], "349200263": [1], "349432199": [1], "349448434": [1], "349729848": [1], "349744568": [1], "349837271": [1], "350037481": [1], "350145260": [1], "350367714": [1], "350672011": [1], "350778467": [1], "350797896": [1], "351000106": [1], "351016405": [1], "351458635": [1], "351694684": [1], "352011518": [1], "352021927": [1], "352233180": [1], "352327351": [1], "352561632": [1], "353526387": [1], "354176154": [1], "354191439": [1], "354221941": [1], "355311276": [1], "355312001": [1], "355869319": [1], "356130237": [1], "356849331": [1], "357015948": [1], "358017376": [1], "358386881": [1], "358699891": [1], "358839777": [1], "358876141": [1], "359733491": [1], "361077899": [1], "361194043": [1], "361544613": [1], "362007372": [1], "362016204": [1], "362056464": [1], "362825019": [1], "362970994": [1], "362983288": [1], "363147748": [1], "363522843": [1], "363719772": [1], "363942408": [1], "364007086": [1], "365690526": [1], "366067451": [1], "366121202": [1], "366350781": [1], "366591470": [1], "366665298": [1], "366972362": [1], "367080185": [1], "367125865": [1], "367605743": [1], "367665682": [1], "367752952": [1], "367835362": [1], "368023959": [1], "368358026": [1], "368740892": [1], "369010829": [1], "369198732": [1], "369290612": [1], "369310973": [1], "369321331": [1], "369839798": [1], "369984623": [1], "370357635": [1], "370370141": [1], "371358567": [1], "371543873": [1], "371670492": [1], "371915552": [1], "372030241": [1], "372047224": [1], "372144871": [1], "373176560": [1], "373416462": [1], "373735149": [1], "373762609": [1], "373871485": [1], "374047782": [1], "374168918": [1], "374227127": [1], "374364782": [1], "374741638": [1], "374927854": [1], "375103163": [1], "375184017": [1], "375261718": [1], "375267369": [1], "375347411": [1], "376409198": [1], "376461094": [1], "376487570": [1], "377015568": [1], "377280416": [1], "377733796": [1], "378002558": [1], "378762257": [1], "379204211": [1], "379599807": [1], "379633869": [1], "379704700": [1], "379827743": [1], "379981628": [1], "380034843": [1], "380092763": [1], "380111630": [1], "380986778": [1], "381243510": [1], "381378126": [1], "381527700": [1], "381929076": [1], "381934617": [1], "382009532": [1], "382228523": [1], "382315625": [1], "382578152": [1], "383101522": [1], "383425518": [1], "383644867": [1], "383839632": [1], "384023192": [1], "384131985": [1], "384559779": [1], "384582770": [1], "384623281": [1], "384814674": [1], "385171568": [1], "386017061": [1], "386229687": [1], "386258341": [1], "386342866": [1], "386703527": [1], "387013106": [1], "387131840": [1], "387145123": [1], "387177900": [1], "387551093": [1], "387634697": [1], "388569409": [1], "388749139": [1], "388787186": [1], "389068300": [1], "389272367": [1], "389410277": [1], "389626504": [1], "389665213": [1], "390841838": [1], "391586965": [1], "392185650": [1], "392375453": [1], "392515607": [1], "393021402": [1], "393406151": [1], "393451741": [1], "393727042": [1], "394258713": [1], "394562314": [1], "394571818": [1], "394580247": [1], "394620804": [1], "394693320": [1], "395007494": [1], "395105965": [1], "395526267": [1], "396714220": [1], "397208823": [1], "397853888": [1], "398289537": [1], "398423181": [1], "398712729": [1], "398717031": [1], "398859609": [1], "399077395": [1], "399106731": [1], "399599943": [1], "400706092": [1], "401035127": [1], "401258631": [1], "401588507": [1], "401841230": [1], "401850763": [1], "401960567": [1], "402517005": [1], "402665391": [1], "403238585": [1], "403715667": [1], "403826595": [1], "404398864": [1], "404818472": [1], "404832278": [1], "405052754": [1], "405330904": [1], "405374285": [1], "405652714": [1], "406220663": [1], "406672526": [1], "406928019": [1], "407185004": [1], "407286846": [1], "407665004": [1], "407751173": [1], "407756956": [1], "407893777": [1], "408398630": [1], "408411629": [1], "408441403": [1], "409069394": [1], "410067928": [1], "410376013": [1], "410832727": [1], "410930210": [1], "411194372": [1], "411310034": [1], "411368828": [1], "411640388": [1], "411968386": [1], "412243044": [1], "412407391": [1], "413131439": [1], "413200087": [1], "413385579": [1], "413620673": [1], "414600029": [1], "414726717": [1], "414936563": [1], "415135939": [1], "415199720": [1], "415414789": [1], "415505383": [1], "415732893": [1], "415802328": [1], "416156447": [1], "416208093": [1], "416573431": [1], "417040884": [1], "417762465": [1], "418033474": [1], "418130083": [1], "418133241": [1], "418193399": [1], "418422085": [1], "418457286": [1], "418587384": [1], "418625186": [1], "418951298": [1], "419513212": [1], "419717049": [1], "420643991": [1], "420704605": [1], "421374620": [1], "421921955": [1], "422084564": [1], "422103004": [1], "422257993": [1], "422742327": [1], "423188560": [1], "423216571": [1], "423814221": [1], "423877339": [1], "424035705": [1], "424499753": [1], "424542595": [1], "424914331": [1], "424915253": [1], "425186852": [1], "425277728": [1], "425639186": [1], "425724988": [1], "425777993": [1], "426006949": [1], "426475305": [1], "428032668": [1], "428271779": [1], "428288078": [1], "428325507": [1], "428504092": [1], "428520937": [1], "428594435": [1], "429263738": [1], "429341087": [1], "429407070": [1], "429712070": [1], "429755246": [1], "429841530": [1], "430080156": [1], "430227829": [1], "431089488": [1], "431094957": [1], "431556814": [1], "431733765": [1], "432152079": [1], "432242266": [1], "432363841": [1], "433456376": [1], "433809484": [1], "434278059": [1], "434398892": [1], "434427454": [1], "434444828": [1], "434822223": [1], "434942319": [1], "435046021": [1], "435207681": [1], "435249142": [1], "435348326": [1], "436123953": [1], "436429385": [1], "436437244": [1], "436633962": [1], "437130654": [1], "437242695": [1], "437272109": [1], "437561777": [1], "437602469": [1], "437623452": [1], "437678864": [1], "437816280": [1], "438022019": [1], "438088030": [1], "438282236": [1], "438287702": [1], "439034169": [1], "439208368": [1], "439582631": [1], "439976993": [1], "440014240": [1], "440099126": [1], "440127214": [1], "440414451": [1], "440415719": [1], "440614632": [1], "440718359": [1], "440819893": [1], "440927497": [1], "441197655": [1], "441200033": [1], "441620880": [1], "442310005": [1], "442687203": [1], "442692304": [1], "442779884": [1], "442797172": [1], "443032083": [1], "443309343": [1], "443415095": [1], "443666102": [1], "443770021": [1], "443864726": [1], "444716226": [1], "444921542": [1], "444984220": [1], "445298325": [1], "445452547": [1], "445496621": [1], "445574989": [1], "445916894": [1], "446013573": [1], "446126589": [1], "446421413": [1], "446683569": [1], "446777669": [1], "447493126": [1], "447605110": [1], "447699753": [1], "447903847": [1], "448081483": [1], "448229579": [1], "448258235": [1], "448523556": [1], "448838339": [1], "448924338": [1], "449419786": [1], "449717239": [1], "449841474": [1], "450684404": [1], "451010885": [1], "451554435": [1], "451572939": [1], "451663477": [1], "451739290": [1], "451764220": [1], "451931614": [1], "452174963": [1], "452346254": [1], "452374066": [1], "452414754": [1], "452440895": [1], "452450475": [1], "452857512": [1], "452903060": [1], "453124021": [1], "453425315": [1], "453765575": [1], "453927043": [1], "454384759": [1], "454610868": [1], "454960420": [1], "455627176": [1], "455838968": [1], "455996362": [1], "456121138": [1], "456193554": [1], "456194603": [1], "456246975": [1], "456498538": [1], "456585048": [1], "456824573": [1], "456890016": [1], "457169202": [1], "457251727": [1], "457367950": [1], "457600179": [1], "457609205": [1], "457758410": [1], "457880819": [1], "457985410": [1], "458035802": [1], "458177236": [1], "458508350": [1], "458975192": [1], "459309512": [1], "459545501": [1], "459579031": [1], "460049144": [1], "460094339": [1], "460227714": [1], "460270818": [1], "460912709": [1], "460948726": [1], "461018833": [1], "461064560": [1], "461358278": [1], "461753783": [1], "462442545": [1], "463011100": [1], "463188565": [1], "463408118": [1], "463408297": [1], "463738815": [1], "463933823": [1], "464318515": [1], "464330160": [1], "464435773": [1], "464487653": [1], "464580010": [1], "464811522": [1], "464857574": [1], "464925550": [1], "465191633": [1], "465360220": [1], "465473170": [1], "465474571": [1], "465645724": [1], "466127943": [1], "466546713": [1], "466702513": [1], "466921855": [1], "467395766": [1], "467698787": [1], "468532014": [1], "468649460": [1], "468837948": [1], "469131882": [1], "469170115": [1], "469355326": [1], "469505410": [1], "469526923": [1], "469586539": [1], "469908906": [1], "469961307": [1], "470471672": [1], "470850954": [1], "470882355": [1], "471451884": [1], "471843704": [1], "471878548": [1], "472207943": [1], "472342906": [1], "472592006": [1], "472850543": [1], "473651950": [1], "473866923": [1], "473884638": [1], "474189798": [1], "474605405": [1], "474622969": [1], "474780850": [1], "475374707": [1], "475640370": [1], "475676432": [1], "475965078": [1], "476038570": [1], "476243917": [1], "477149380": [1], "477317005": [1], "477486481": [1], "477564703": [1], "477807947": [1], "477810283": [1], "478286317": [1], "478567971": [1], "478647140": [1], "478786936": [1], "479391271": [1], "479638330": [1], "479809018": [1], "480471765": [1], "480516779": [1], "480520555": [1], "481310968": [1], "483765358": [1], "483858726": [1], "484138946": [1], "484175835": [1], "484234602": [1], "484244960": [1], "484367693": [1], "484381365": [1], "484699798": [1], "485879450": [1], "486174624": [1], "486461606": [1], "486585507": [1], "486604540": [1], "486741690": [1], "487044088": [1], "487141785": [1], "487190365": [1], "487390770": [1], "487515052": [1], "487884354": [1], "487997907": [1], "488135526": [1], "488200660": [1], "488201794": [1], "488571957": [1], "488861804": [1], "488905388": [1], "489125292": [1], "489269942": [1], "489849805": [1], "490022398": [1], "490057379": [1], "490199867": [1], "490240526": [1], "490298292": [1], "490325394": [1], "490525794": [1], "490655532": [1], "490940901": [1], "491011874": [1], "491061572": [1], "491608791": [1], "491899013": [1], "491994525": [1], "492152084": [1], "492176876": [1], "492667729": [1], "492714997": [1], "493799589": [1], "493829622": [1], "494674973": [1], "494689539": [1], "494863664": [1], "494910035": [1], "494926789": [1], "495122520": [1], "495137188": [1], "495348861": [1], "495974299": [1], "496091323": [1], "496287756": [1], "496492836": [1], "496534947": [1], "496808882": [1], "497041317": [1], "497043563": [1], "497192530": [1], "497411658": [1], "497621318": [1], "497925017": [1], "498278891": [1], "498426236": [1], "499145424": [1], "499146467": [1], "499365208": [1], "499502273": [1], "499607570": [1], "499747194": [1], "499783310": [1], "500400992": [1], "500410899": [1], "501082393": [1], "501174878": [1], "501198393": [1], "501321191": [1], "501395693": [1], "501417761": [1], "501687005": [1], "501922585": [1], "502451274": [1], "502993382": [1], "503155584": [1], "503635254": [1], "503759498": [1], "503988660": [1], "503999961": [1], "504191666": [1], "504433338": [1], "504531591": [1], "504943233": [1], "505297526": [1], "505423454": [1], "505781258": [1], "506004449": [1], "506021217": [1], "506095525": [1], "507047306": [1], "507131700": [1], "507512919": [1], "507800350": [1], "507880280": [1], "507881350": [1], "508133226": [1], "508150298": [1], "508156320": [1], "508356929": [1], "508413017": [1], "508791895": [1], "508813314": [1], "508823016": [1], "508892360": [1], "509141241": [1], "509821143": [1], "510191157": [1], "510239641": [1], "511053039": [1], "511428547": [1], "511521534": [1], "511616906": [1], "511713938": [1], "511792455": [1], "511814114": [1], "512779255": [1], "512825863": [1], "512924166": [1], "512930648": [1], "512942856": [1], "513097646": [1], "513920031": [1], "513960896": [1], "513970332": [1], "514108163": [1], "514346238": [1], "514403148": [1], "514436315": [1], "514684569": [1], "514764607": [1], "514775637": [1], "515075819": [1], "515399822": [1], "515495522": [1], "515496807": [1], "515574407": [1], "515583095": [1], "515764080": [1], "515885326": [1], "516433666": [1], "516455312": [1], "516466703": [1], "516649036": [1], "516695126": [1], "516940895": [1], "517281822": [1], "517285489": [1], "517917179": [1], "518146748": [1], "518611666": [1], "518636533": [1], "518937671": [1], "519350549": [1], "519388196": [1], "519690871": [1], "520246004": [1], "521914075": [1], "522235917": [1], "522247360": [1], "522395583": [1], "522519439": [1], "522740100": [1], "522872721": [1], "523009950": [1], "523035192": [1], "523212717": [1], "523291620": [1], "523313573": [1], "523356277": [1], "523457035": [1], "523559316": [1], "524149617": [1], "524429179": [1], "524519706": [1], "524561926": [1], "524882537": [1], "525245038": [1], "525348560": [1], "525650870": [1], "525992547": [1], "526669059": [1], "526693990": [1], "526955120": [1], "527128527": [1], "527147049": [1], "527384619": [1], "527419726": [1], "527538518": [1], "528175556": [1], "529160898": [1], "529192568": [1], "529227858": [1], "529573642": [1], "529582633": [1], "529874054": [1], "529916480": [1], "529985399": [1], "530419815": [1], "530554750": [1], "530671542": [1], "530722811": [1], "531034434": [1], "531147337": [1], "531291769": [1], "531371317": [1], "532047880": [1], "532339281": [1], "532561300": [1], "532838222": [1], "532984460": [1], "533229935": [1], "533419811": [1], "533527730": [1], "533870108": [1], "534076208": [1], "534096612": [1], "534288183": [1], "534622781": [1], "534985396": [1], "535334160": [1], "535391350": [1], "535997397": [1], "536805018": [1], "536858287": [1], "537433267": [1], "537505165": [1], "537672301": [1], "537802512": [1], "538067647": [1], "538165289": [1], "538201744": [1], "539057105": [1], "539586055": [1], "539949567": [1], "540111431": [1], "540415451": [1], "540449122": [1], "540707107": [1], "541112297": [1], "541381286": [1], "541536341": [1], "542530007": [1], "542735653": [1], "542832622": [1], "543092503": [1], "543323826": [1], "543423522": [1], "543820435": [1], "543826409": [1], "544119406": [1], "544223997": [1], "544695151": [1], "544760772": [1], "544878642": [1], "545056325": [1], "545098968": [1], "545228608": [1], "545285448": [1], "545615956": [1], "546366808": [1], "546578209": [1], "547175387": [1], "547489168": [1], "547525678": [1], "547945308": [1], "548100065": [1], "548106348": [1], "548272819": [1], "548657777": [1], "548725826": [1], "548853128": [1], "548915106": [1], "549094808": [1], "549417872": [1], "549520070": [1], "549925845": [1], "550660129": [1], "550882606": [1], "550959524": [1], "550992177": [1], "551072603": [1], "551354543": [1], "551793753": [1], "551803462": [1], "551812202": [1], "551886660": [1], "551897796": [1], "551943276": [1], "552220286": [1], "552421451": [1], "552692475": [1], "552923511": [1], "553067399": [1], "553362197": [1], "554043714": [1], "554452826": [1], "555493585": [1], "555527129": [1], "555711003": [1], "556323335": [1], "556442545": [1], "556772613": [1], "557456091": [1], "557745530": [1], "557790020": [1], "557898494": [1], "557927409": [1], "558068747": [1], "558708320": [1], "558808400": [1], "558812719": [1], "559215869": [1], "559476811": [1], "560371163": [1], "560994900": [1], "561160740": [1], "561205161": [1], "561241381": [1], "561310000": [1], "561728782": [1], "562150982": [1], "562203859": [1], "562554588": [1], "562676257": [1], "562738872": [1], "564082476": [1], "564240179": [1], "564564504": [1], "564574965": [1], "564673119": [1], "564809007": [1], "565268822": [1], "566050028": [1], "566148498": [1], "566479377": [1], "566704912": [1], "566754653": [1], "566820257": [1], "567083185": [1], "567737645": [1], "567777844": [1], "567849128": [1], "568423777": [1], "568556962": [1], "568963271": [1], "569049851": [1], "569182318": [1], "569257791": [1], "569471858": [1], "569617886": [1], "569892132": [1], "570307602": [1], "571449300": [1], "571756566": [1], "572011096": [1], "572403450": [1], "573447201": [1], "574427736": [1], "574826588": [1], "574984197": [1], "575041316": [1], "575352985": [1], "575488674": [1], "575613710": [1], "575745367": [1], "576550049": [1], "576966577": [1], "577428564": [1], "577964611": [1], "578297194": [1], "578297411": [1], "578373462": [1], "578499211": [1], "578618548": [1], "578709786": [1], "578721543": [1], "578746730": [1], "578833524": [1], "578954205": [1], "579092038": [1], "579624297": [1], "579694532": [1], "579923383": [1], "579957468": [1], "580436516": [1], "580841255": [1], "581542537": [1], "582008644": [1], "582151001": [1], "582941167": [1], "583059323": [1], "583278275": [1], "583663471": [1], "583697419": [1], "583747076": [1], "584624132": [1], "585411355": [1], "585573416": [1], "585757093": [1], "585831272": [1], "585895434": [1], "585901385": [1], "586142631": [1], "586479597": [1], "587081465": [1], "587149535": [1], "587867589": [1], "587969367": [1], "588039279": [1], "588913426": [1], "589097075": [1], "589371364": [1], "589593222": [1], "589711949": [1], "589843165": [1], "590030605": [1], "591034498": [1], "591525727": [1], "591662791": [1], "591879516": [1], "592357239": [1], "592789415": [1], "592864298": [1], "593141723": [1], "593279465": [1], "593436307": [1], "593632375": [1], "593802915": [1], "593842312": [1], "594741214": [1], "594853270": [1], "595026760": [1], "595156996": [1], "595157305": [1], "595456751": [1], "596252263": [1], "596672051": [1], "596889454": [1], "596906396": [1], "597032401": [1], "597191232": [1], "597797777": [1], "598061374": [1], "598300081": [1], "598495366": [1], "598710303": [1], "598729586": [1], "599173292": [1], "599718875": [1], "600111699": [1], "600223574": [1], "600500841": [1], "600504358": [1], "600721765": [1], "601077743": [1], "601392460": [1], "601551986": [1], "601648662": [1], "601888029": [1], "602363144": [1], "602526309": [1], "602849812": [1], "602941949": [1], "603301823": [1], "603340800": [1], "603828583": [1], "604069656": [1], "604133462": [1], "604256551": [1], "604437094": [1], "604455994": [1], "604797243": [1], "605073853": [1], "605151926": [1], "605210919": [1], "605462510": [1], "605486405": [1], "605897509": [1], "606084640": [1], "606341607": [1], "606630953": [1], "606759742": [1], "606771528": [1], "606886845": [1], "606925884": [1], "607234973": [1], "607721427": [1], "607870607": [1], "607916691": [1], "608046004": [1], "608492204": [1], "608629466": [1], "609564046": [1], "609665532": [1], "610328625": [1], "610391882": [1], "611118062": [1], "611491020": [1], "611586485": [1], "611677757": [1], "611707495": [1], "611762705": [1], "612019732": [1], "612277055": [1], "612532321": [1], "612657319": [1], "613033386": [1], "613644502": [1], "614181263": [1], "614186499": [1], "614374927": [1], "614482496": [1], "614637371": [1], "614826436": [1], "615030205": [1], "615123105": [1], "615377295": [1], "615422804": [1], "615582502": [1], "615593234": [1], "615652911": [1], "615769453": [1], "615861691": [1], "615889186": [1], "615956902": [1], "615969593": [1], "616055230": [1], "616491412": [1], "616995383": [1], "617500296": [1], "617509244": [1], "617620165": [1], "617689230": [1], "618125337": [1], "618189933": [1], "618730394": [1], "618761908": [1], "618917572": [1], "619179841": [1], "619468062": [1], "619487221": [1], "619778690": [1], "619822089": [1], "620437645": [1], "621215593": [1], "621486227": [1], "621661038": [1], "622082670": [1], "622109719": [1], "622376151": [1], "622449534": [1], "622977929": [1], "623405816": [1], "623617559": [1], "624145372": [1], "624592013": [1], "624597430": [1], "624754141": [1], "624885510": [1], "624973797": [1], "625459193": [1], "625463689": [1], "625674842": [1], "625820976": [1], "626302255": [1], "626329940": [1], "626536462": [1], "626539686": [1], "626755378": [1], "626809140": [1], "626900024": [1], "626935355": [1], "626960266": [1], "627069271": [1], "627094353": [1], "627139518": [1], "627683580": [1], "627741698": [1], "628672370": [1], "628974948": [1], "629060868": [1], "629262328": [1], "629272613": [1], "629275666": [1], "629424263": [1], "629615210": [1], "629784737": [1], "630102764": [1], "630451660": [1], "630867417": [1], "631043355": [1], "631294240": [1], "631646349": [1], "631740118": [1], "631999647": [1], "632214272": [1], "632600767": [1], "634457966": [1], "634697628": [1], "635016370": [1], "635298930": [1], "635541329": [1], "635569103": [1], "635587887": [1], "635904275": [1], "636186250": [1], "636835192": [1], "636967831": [1], "637441288": [1], "637617024": [1], "638030195": [1], "638181743": [1], "638714574": [1], "639048651": [1], "639278742": [1], "639750936": [1], "639973650": [1], "640336458": [1], "640688495": [1], "641103227": [1], "641260743": [1], "641395886": [1], "641413035": [1], "641490494": [1], "641617959": [1], "641778922": [1], "641879101": [1], "642008706": [1], "642177556": [1], "642419086": [1], "642846451": [1], "642942778": [1], "642981967": [1], "643326814": [1], "643682181": [1], "643688560": [1], "644156708": [1], "644723805": [1], "644776225": [1], "644964665": [1], "645141349": [1], "645209981": [1], "645568336": [1], "646102139": [1], "646236358": [1], "646292077": [1], "646487471": [1], "646753078": [1], "646851026": [1], "646917472": [1], "647720086": [1], "648436801": [1], "648493613": [1], "648548799": [1], "648697899": [1], "649032911": [1], "649067981": [1], "649127266": [1], "649336969": [1], "649872342": [1], "649971887": [1], "650311086": [1], "650470943": [1], "650518874": [1], "650565633": [1], "650758049": [1], "650934051": [1], "651077489": [1], "651164611": [1], "651588773": [1], "651884206": [1], "652288498": [1], "653035600": [1], "653309644": [1], "653448663": [1], "653476854": [1], "653768591": [1], "653844547": [1], "653912256": [1], "654287237": [1], "654355198": [1], "654450973": [1], "654706938": [1], "655130596": [1], "655499510": [1], "655591458": [1], "656153830": [1], "656424539": [1], "656555755": [1], "656709579": [1], "656896079": [1], "657054155": [1], "657349680": [1], "657888105": [1], "658091727": [1], "658094698": [1], "658099558": [1], "658300188": [1], "658415009": [1], "658417948": [1], "658457860": [1], "658495576": [1], "658766729": [1], "659374701": [1], "659476939": [1], "659688147": [1], "659735443": [1], "660576392": [1], "660604784": [1], "660795372": [1], "661256487": [1], "661629580": [1], "661832581": [1], "661860388": [1], "662059713": [1], "662113773": [1], "662132933": [1], "662156838": [1], "662297387": [1], "662589420": [1], "662757887": [1], "663070993": [1], "663198393": [1], "663216536": [1], "663244686": [1], "663648825": [1], "663712118": [1], "663743645": [1], "664092560": [1], "664153861": [1], "664606750": [1], "665249343": [1], "665649533": [1], "666080937": [1], "666107247": [1], "666516647": [1], "667064994": [1], "667596850": [1], "667802791": [1], "667814473": [1], "668210611": [1], "668434927": [1], "668488486": [1], "668489705": [1], "668604709": [1], "668634019": [1], "669231167": [1], "669356800": [1], "671134002": [1], "671491141": [1], "671954342": [1], "672104954": [1], "672151013": [1], "672275126": [1], "672323843": [1], "672374736": [1], "672648052": [1], "672664672": [1], "673063123": [1], "673124257": [1], "673162438": [1], "673313674": [1], "673681789": [1], "674109204": [1], "674551840": [1], "675100139": [1], "675358742": [1], "675966819": [1], "675987341": [1], "676168521": [1], "676220238": [1], "676262097": [1], "676470160": [1], "676684288": [1], "678105046": [1], "678449141": [1], "678507926": [1], "678564089": [1], "678702985": [1], "678907533": [1], "680083564": [1], "680140876": [1], "680512868": [1], "680751391": [1], "681081254": [1], "681259971": [1], "681323127": [1], "681480107": [1], "681591041": [1], "682237674": [1], "682669435": [1], "683207859": [1], "683212787": [1], "683218613": [1], "683230351": [1], "683587869": [1], "683848107": [1], "683932541": [1], "684347424": [1], "684423279": [1], "684562024": [1], "684597751": [1], "684624648": [1], "684761503": [1], "685776354": [1], "686316691": [1], "686472689": [1], "686592390": [1], "686731846": [1], "686848870": [1], "687196460": [1], "687519096": [1], "687575478": [1], "687706564": [1], "687736132": [1], "688046915": [1], "688234658": [1], "688263777": [1], "688345391": [1], "688348943": [1], "688723019": [1], "688738656": [1], "689050612": [1], "689063781": [1], "689388559": [1], "689412427": [1], "689542849": [1], "689850223": [1], "689946730": [1], "690772134": [1], "691183449": [1], "691311548": [1], "691589556": [1], "691739287": [1], "691971135": [1], "692219787": [1], "692340729": [1], "692479919": [1], "692484522": [1], "692744224": [1], "693157852": [1], "693559241": [1], "693625665": [1], "693951584": [1], "694874299": [1], "695275674": [1], "695373188": [1], "695392406": [1], "695451593": [1], "695929210": [1], "695953554": [1], "696002128": [1], "696321713": [1], "696328910": [1], "696450838": [1], "696853318": [1], "696995279": [1], "697400698": [1], "697619946": [1], "697668407": [1], "697681069": [1], "697762945": [1], "697766116": [1], "697829339": [1], "697988899": [1], "698081688": [1], "698098769": [1], "698359383": [1], "699067236": [1], "699120376": [1], "699534470": [1], "699655105": [1], "699724170": [1], "700059250": [1], "700414171": [1], "700761714": [1], "701592270": [1], "701627690": [1], "701637799": [1], "701933559": [1], "702014716": [1], "702214722": [1], "702522580": [1], "703029725": [1], "703067116": [1], "703843114": [1], "703972532": [1], "704279420": [1], "704282217": [1], "704299383": [1], "704313938": [1], "704422687": [1], "704427261": [1], "705143780": [1], "705405287": [1], "705522774": [1], "705582746": [1], "705894134": [1], "706163578": [1], "706247581": [1], "706351627": [1], "706513973": [1], "707373027": [1], "707444795": [1], "707837660": [1], "707984181": [1], "708364464": [1], "708386299": [1], "708478266": [1], "708483033": [1], "708546058": [1], "708782533": [1], "709196732": [1], "710016547": [1], "710223172": [1], "710244220": [1], "710483101": [1], "710488896": [1], "710918380": [1], "711054380": [1], "711332305": [1], "711481836": [1], "711486111": [1], "711709750": [1], "711784230": [1], "712138820": [1], "712582212": [1], "712688719": [1], "713595728": [1], "713691196": [1], "714617743": [1], "714970385": [1], "715013814": [1], "715024874": [1], "715134116": [1], "715356629": [1], "715376732": [1], "715578365": [1], "715781185": [1], "716035720": [1], "716042361": [1], "716509057": [1], "716663409": [1], "717072289": [1], "717392271": [1], "717470662": [1], "717764194": [1], "717832433": [1], "717861311": [1], "717939993": [1], "717950008": [1], "718965771": [1], "719237775": [1], "719260634": [1], "719426054": [1], "719750114": [1], "719775037": [1], "719860597": [1], "719966981": [1], "720013622": [1], "720412619": [1], "720445637": [1], "720730170": [1], "720768278": [1], "721445598": [1], "721637235": [1], "721835413": [1], "721902411": [1], "722045881": [1], "722171213": [1], "722200191": [1], "722444541": [1], "722521072": [1], "722759284": [1], "722785546": [1], "723020020": [1], "723286092": [1], "723411560": [1], "723493161": [1], "723527434": [1], "723820403": [1], "724180064": [1], "724297249": [1], "724645152": [1], "724805446": [1], "725471552": [1], "725573488": [1], "725764745": [1], "725901761": [1], "725953250": [1], "726095892": [1], "726243145": [1], "726268335": [1], "726408265": [1], "726507238": [1], "726523192": [1], "726852112": [1], "726855867": [1], "726917289": [1], "727189386": [1], "727294826": [1], "727699411": [1], "727936789": [1], "728135616": [1], "728341849": [1], "728358524": [1], "728849207": [1], "728919212": [1], "728953374": [1], "729028251": [1], "729559904": [1], "729720353": [1], "729812815": [1], "730306908": [1], "730389411": [1], "730650402": [1], "730911138": [1], "731523007": [1], "731580527": [1], "731970196": [1], "732544289": [1], "732789634": [1], "732851011": [1], "733089798": [1], "733126532": [1], "733695530": [1], "733706557": [1], "734338568": [1], "734368632": [1], "734432572": [1], "734686317": [1], "734844569": [1], "734880719": [1], "734888269": [1], "734951859": [1], "735124802": [1], "735675590": [1], "735782836": [1], "735803319": [1], "736053885": [1], "736148607": [1], "736193033": [1], "736330679": [1], "736626016": [1], "736702531": [1], "736713829": [1], "738014301": [1], "738272523": [1], "738287648": [1], "738884980": [1], "739006983": [1], "739203791": [1], "739502014": [1], "739813913": [1], "739855105": [1], "740273448": [1], "740367823": [1], "740709920": [1], "740791680": [1], "741322277": [1], "741882144": [1], "741946357": [1], "742055199": [1], "742286322": [1], "742942584": [1], "743081055": [1], "743102809": [1], "743197265": [1], "743300325": [1], "743324689": [1], "743603414": [1], "743719517": [1], "744408332": [1], "744441737": [1], "744586776": [1], "745286869": [1], "745571452": [1], "746031075": [1], "746276046": [1], "746603236": [1], "747601684": [1], "747724085": [1], "747793050": [1], "748025282": [1], "748588412": [1], "749242723": [1], "749250238": [1], "749407286": [1], "750069311": [1], "750275683": [1], "750629152": [1], "751281342": [1], "751295227": [1], "751302700": [1], "751345215": [1], "751444751": [1], "751553058": [1], "751568406": [1], "752007528": [1], "752206061": [1], "752212193": [1], "752486174": [1], "752497173": [1], "752510861": [1], "752575524": [1], "752983151": [1], "753057783": [1], "753206006": [1], "753604658": [1], "753937786": [1], "754637090": [1], "754762951": [1], "754768776": [1], "754967712": [1], "755059645": [1], "755562713": [1], "755584170": [1], "755677115": [1], "756438582": [1], "756464310": [1], "756938487": [1], "757419176": [1], "757497854": [1], "757537841": [1], "757641310": [1], "757778352": [1], "758899254": [1], "759309455": [1], "759981158": [1], "760319182": [1], "760695137": [1], "761051685": [1], "761245939": [1], "761268448": [1], "761342812": [1], "761676028": [1], "761908341": [1], "762422490": [1], "762598762": [1], "762629216": [1], "762920603": [1], "763294721": [1], "763576049": [1], "763844630": [1], "764462972": [1], "764634212": [1], "764762129": [1], "765241741": [1], "765783088": [1], "765855868": [1], "765911533": [1], "765988779": [1], "766268747": [1], "766655142": [1], "766998798": [1], "768187504": [1], "768389792": [1], "768706831": [1], "768751075": [1], "768925685": [1], "768945251": [1], "769381981": [1], "769580223": [1], "769809736": [1], "769863408": [1], "769982754": [1], "770296427": [1], "770393395": [1], "770849514": [1], "770917537": [1], "770974208": [1], "771264313": [1], "771332371": [1], "771527954": [1], "771630476": [1], "772184651": [1], "772233023": [1], "772288423": [1], "772869232": [1], "773107436": [1], "773107666": [1], "773187618": [1], "773197811": [1], "773886002": [1], "774218089": [1], "774254041": [1], "774586523": [1], "774674508": [1], "774874071": [1], "775185794": [1], "775514368": [1], "775710300": [1], "776200469": [1], "776548794": [1], "776564276": [1], "776628414": [1], "776726142": [1], "777176089": [1], "777221132": [1], "777458294": [1], "777602286": [1], "777781125": [1], "777825649": [1], "778100878": [1], "778249688": [1], "778264686": [1], "778666636": [1], "778899121": [1], "778933619": [1], "779052040": [1], "779304061": [1], "779367898": [1], "779558244": [1], "779824757": [1], "779891326": [1], "780188951": [1], "780433722": [1], "780687805": [1], "780766303": [1], "780830222": [1], "780931550": [1], "781142596": [1], "781177552": [1], "781252185": [1], "781393590": [1], "781580598": [1], "781986974": [1], "782207920": [1], "782243901": [1], "782719814": [1], "782725490": [1], "783046461": [1], "783176162": [1], "783385886": [1], "783657379": [1], "783687212": [1], "783738072": [1], "783841932": [1], "783992652": [1], "784049726": [1], "784448792": [1], "784481875": [1], "785393386": [1], "785537807": [1], "785718307": [1], "785780191": [1], "785900158": [1], "786025949": [1], "786191852": [1], "786484963": [1], "786799243": [1], "786809080": [1], "786824889": [1], "786912849": [1], "787118098": [1], "787353826": [1], "788369240": [1], "788510900": [1], "788880699": [1], "788949860": [1], "789075205": [1], "789335793": [1], "789727083": [1], "789937116": [1], "790270609": [1], "790424983": [1], "790569080": [1], "790953735": [1], "791024072": [1], "791104980": [1], "791180604": [1], "791214929": [1], "791773692": [1], "792004623": [1], "792008085": [1], "793072268": [1], "793077498": [1], "793590835": [1], "793659390": [1], "794042763": [1], "794256296": [1], "794508549": [1], "794905151": [1], "795230301": [1], "795386973": [1], "795839111": [1], "796058544": [1], "796736636": [1], "797232228": [1], "797245099": [1], "797396755": [1], "797646200": [1], "797652463": [1], "797983604": [1], "798413210": [1], "798785365": [1], "798871656": [1], "799013063": [1], "799272531": [1], "799368226": [1], "799417427": [1], "799521325": [1], "799654471": [1], "800240360": [1], "800621183": [1], "801258558": [1], "801678698": [1], "801685465": [1], "801782525": [1], "802145062": [1], "802180798": [1], "802570397": [1], "802720235": [1], "802734290": [1], "802758314": [1], "802857343": [1], "803077503": [1], "803385428": [1], "803609063": [1], "803662203": [1], "803764375": [1], "803765593": [1], "803959903": [1], "804196654": [1], "804347644": [1], "804575221": [1], "804779315": [1], "805210066": [1], "805719717": [1], "805726135": [1], "805812030": [1], "805816179": [1], "805874359": [1], "805892310": [1], "806099542": [1], "806107616": [1], "806514400": [1], "806768313": [1], "806788821": [1], "806903788": [1], "806967376": [1], "807077422": [1], "807126419": [1], "807522065": [1], "808251733": [1], "808431651": [1], "808565575": [1], "808660090": [1], "809938216": [1], "809961601": [1], "810017572": [1], "810338071": [1], "810665679": [1], "810685283": [1], "811412867": [1], "811572795": [1], "811730175": [1], "811976039": [1], "812121711": [1], "812160993": [1], "812608101": [1], "812911589": [1], "813282947": [1], "813524478": [1], "813594665": [1], "813858350": [1], "813878278": [1], "813886430": [1], "814207378": [1], "814292800": [1], "814417111": [1], "814512904": [1], "815158599": [1], "815926670": [1], "816090590": [1], "816266615": [1], "816379312": [1], "816862429": [1], "816963228": [1], "816984119": [1], "817039108": [1], "817191365": [1], "817519083": [1], "817662611": [1], "818014280": [1], "818054004": [1], "818786247": [1], "819107686": [1], "819444882": [1], "820053469": [1], "820086450": [1], "820912597": [1], "821612826": [1], "821940249": [1], "822462153": [1], "823025493": [1], "823510159": [1], "823851616": [1], "824878943": [1], "825479745": [1], "825511765": [1], "825521298": [1], "825783075": [1], "825852827": [1], "825967393": [1], "826011203": [1], "826191490": [1], "826248441": [1], "826833915": [1], "826926348": [1], "827396252": [1], "827516214": [1], "827681786": [1], "827776152": [1], "828133547": [1], "828531872": [1], "828638311": [1], "828722702": [1], "829290662": [1], "829516931": [1], "829658748": [1], "830028931": [1], "830474978": [1], "830503549": [1], "830568262": [1], "830586973": [1], "830638063": [1], "830982746": [1], "831145417": [1], "831542898": [1], "831602316": [1], "831827467": [1], "831920732": [1], "831958642": [1], "832026031": [1], "832092776": [1], "832537703": [1], "832913319": [1], "832959406": [1], "833600270": [1], "833624839": [1], "833842175": [1], "834069207": [1], "834122525": [1], "835853181": [1], "836085409": [1], "836218245": [1], "836421531": [1], "836756878": [1], "836968762": [1], "837290582": [1], "837375556": [1], "837579537": [1], "837745080": [1], "837771638": [1], "837903227": [1], "837952809": [1], "837954444": [1], "838137249": [1], "838451314": [1], "838451563": [1], "838512417": [1], "838643752": [1], "838790339": [1], "839158014": [1], "839416101": [1], "839872285": [1], "840281223": [1], "840604107": [1], "840609087": [1], "840635980": [1], "840782635": [1], "841170457": [1], "841278587": [1], "842152370": [1], "842670119": [1], "842729766": [1], "842757747": [1], "842825330": [1], "842907079": [1], "843255408": [1], "843259811": [1], "843453289": [1], "843588222": [1], "843879276": [1], "844206710": [1], "844265361": [1], "845524080": [1], "845660733": [1], "845943243": [1], "846036074": [1], "846155805": [1], "846495818": [1], "846603276": [1], "846671598": [1], "846790308": [1], "846842459": [1], "846982291": [1], "847031281": [1], "847508267": [1], "848394997": [1], "848404002": [1], "848962773": [1], "849018465": [1], "849239605": [1], "849487025": [1], "849716035": [1], "849837310": [1], "849852881": [1], "850719286": [1], "851205054": [1], "851492317": [1], "851525679": [1], "851841921": [1], "852394644": [1], "852875797": [1], "853024973": [1], "853319030": [1], "853578464": [1], "853621942": [1], "853699321": [1], "853804814": [1], "854188309": [1], "854460722": [1], "854872634": [1], "854947809": [1], "854967800": [1], "855065954": [1], "855162100": [1], "855510808": [1], "855877763": [1], "856156761": [1], "856345220": [1], "856395293": [1], "856652061": [1], "856872796": [1], "856883506": [1], "857087327": [1], "857384333": [1], "857521141": [1], "857924250": [1], "857935550": [1], "857948842": [1], "857959154": [1], "858183016": [1], "858577273": [1], "858712948": [1], "858925443": [1], "859401515": [1], "860611336": [1], "860695888": [1], "860808431": [1], "860857565": [1], "860973389": [1], "861111191": [1], "861244507": [1], "861305547": [1], "861512732": [1], "862061879": [1], "862406812": [1], "862766161": [1], "863013707": [1], "863324531": [1], "863525406": [1], "864078281": [1], "864704908": [1], "864891400": [1], "865003217": [1], "865291063": [1], "865398369": [1], "865414645": [1], "865446219": [1], "865532223": [1], "865802652": [1], "865883087": [1], "866011891": [1], "866817653": [1], "867150841": [1], "867347481": [1], "867737720": [1], "868262352": [1], "868392097": [1], "869134659": [1], "869542467": [1], "869734326": [1], "870451192": [1], "870519866": [1], "870585124": [1], "871147920": [1], "871288316": [1], "871366047": [1], "871716659": [1], "871838156": [1], "872193597": [1], "872320784": [1], "874096105": [1], "874793344": [1], "875789688": [1], "875982276": [1], "876010655": [1], "876264513": [1], "876540404": [1], "876981007": [1], "877168381": [1], "877245743": [1], "877436328": [1], "877473850": [1], "877498604": [1], "878526708": [1], "878758161": [1], "878799543": [1], "879230035": [1], "879748820": [1], "879917339": [1], "879964142": [1], "880036003": [1], "880042977": [1], "880149261": [1], "880195542": [1], "880664583": [1], "880787390": [1], "881397935": [1], "881419928": [1], "881508668": [1], "881717238": [1], "881836407": [1], "882025241": [1], "882060810": [1], "882109638": [1], "882362729": [1], "882547540": [1], "882725440": [1], "882755016": [1], "882856145": [1], "882856501": [1], "882997408": [1], "883111497": [1], "883373112": [1], "883456039": [1], "883559785": [1], "884153907": [1], "884912734": [1], "884937545": [1], "884982586": [1], "885041429": [1], "885147930": [1], "885197192": [1], "885396393": [1], "885421318": [1], "885425467": [1], "885811966": [1], "886113930": [1], "886118547": [1], "886236199": [1], "886278876": [1], "886320515": [1], "886328792": [1], "886461396": [1], "886694042": [1], "886835306": [1], "887160285": [1], "887460767": [1], "887511728": [1], "888053029": [1], "888104118": [1], "888234475": [1], "888451946": [1], "888459734": [1], "888604411": [1], "888753590": [1], "888870100": [1], "888915589": [1], "889426410": [1], "889428235": [1], "889638510": [1], "890293427": [1], "890317908": [1], "890941083": [1], "891334579": [1], "891642082": [1], "891663716": [1], "891827994": [1], "892235667": [1], "892464793": [1], "892523194": [1], "892742555": [1], "892756913": [1], "893181560": [1], "893288448": [1], "893339262": [1], "893342944": [1], "893679532": [1], "893754669": [1], "893885411": [1], "894614812": [1], "894752429": [1], "894978579": [1], "895058638": [1], "895355234": [1], "895669709": [1], "895894650": [1], "896011915": [1], "896112572": [1], "896581783": [1], "896736563": [1], "896880384": [1], "896919090": [1], "897044808": [1], "897390665": [1], "897617907": [1], "898026811": [1], "898576725": [1], "898594385": [1], "898701002": [1], "898791944": [1], "899009930": [1], "899046625": [1], "899470606": [1], "899617756": [1], "899711819": [1], "900089471": [1], "900468408": [1], "901123682": [1], "901132409": [1], "901136606": [1], "901342549": [1], "901416515": [1], "901598317": [1], "901880274": [1], "902211317": [1], "902924315": [1], "902997302": [1], "903365641": [1], "903392904": [1], "903447285": [1], "903609443": [1], "903652761": [1], "903943620": [1], "904269734": [1], "904404557": [1], "905011896": [1], "905355298": [1], "905904458": [1], "906054846": [1], "906103975": [1], "907200545": [1], "907379198": [1], "907481067": [1], "907509658": [1], "907921850": [1], "908165155": [1], "908385836": [1], "908836931": [1], "909180143": [1], "909244874": [1], "909392379": [1], "910231807": [1], "910385493": [1], "910568076": [1], "910588288": [1], "910721583": [1], "911239765": [1], "911974540": [1], "912122106": [1], "912361254": [1], "912616792": [1], "912850857": [1], "913109354": [1], "913120597": [1], "913223206": [1], "913246894": [1], "913382513": [1], "913660654": [1], "913999295": [1], "914022872": [1], "914023861": [1], "914137404": [1], "914228920": [1], "914567182": [1], "914900885": [1], "915209906": [1], "915418269": [1], "915445191": [1], "915597409": [1], "916037124": [1], "916085770": [1], "916189781": [1], "917014038": [1], "917108512": [1], "917350532": [1], "917878782": [1], "917921451": [1], "918295452": [1], "918581835": [1], "919131438": [1], "919321555": [1], "919403600": [1], "919436499": [1], "919565837": [1], "920743731": [1], "920921767": [1], "920959216": [1], "921043311": [1], "921272815": [1], "921524062": [1], "921533295": [1], "921817256": [1], "922098428": [1], "922277728": [1], "922401887": [1], "922787767": [1], "922795070": [1], "923232638": [1], "923269330": [1], "923297798": [1], "923815157": [1], "923929160": [1], "924063280": [1], "924353692": [1], "924492437": [1], "924847210": [1], "925382382": [1], "925515144": [1], "926608505": [1], "926747567": [1], "926761097": [1], "926820080": [1], "927278751": [1], "927746581": [1], "927817144": [1], "928154009": [1], "928286882": [1], "928413318": [1], "928554327": [1], "929446291": [1], "929893448": [1], "930321003": [1], "930470824": [1], "930614776": [1], "930915112": [1], "931508892": [1], "931787765": [1], "932048985": [1], "932128523": [1], "933439351": [1], "933534630": [1], "933641366": [1], "933646942": [1], "934141600": [1], "934205544": [1], "934328994": [1], "934378731": [1], "934607856": [1], "934765266": [1], "934841097": [1], "935194766": [1], "935986174": [1], "936234656": [1], "936284691": [1], "936539477": [1], "936660707": [1], "936730589": [1], "936743209": [1], "937001305": [1], "937140159": [1], "937152628": [1], "937194745": [1], "937478334": [1], "937625401": [1], "937915099": [1], "938097936": [1], "938098537": [1], "938273364": [1], "938802839": [1], "938806982": [1], "938954601": [1], "939035188": [1], "939126581": [1], "939428884": [1], "939480206": [1], "940085430": [1], "940116084": [1], "940285769": [1], "940341346": [1], "940604765": [1], "940771764": [1], "941192736": [1], "941235679": [1], "941949970": [1], "942333729": [1], "942564224": [1], "942584313": [1], "942829354": [1], "943163941": [1], "943266417": [1], "943317895": [1], "943559045": [1], "943756359": [1], "944026129": [1], "944065090": [1], "944120712": [1], "944122112": [1], "944323877": [1], "944753650": [1], "944781741": [1], "944802027": [1], "944830593": [1], "944840018": [1], "944898687": [1], "945071622": [1], "945198637": [1], "945298411": [1], "945302879": [1], "945816135": [1], "945885254": [1], "946001524": [1], "946376788": [1], "946591045": [1], "947012704": [1], "947629769": [1], "948575686": [1], "948936915": [1], "949301415": [1], "949416087": [1], "949444479": [1], "949919741": [1], "949997028": [1], "950199816": [1], "950307048": [1], "950802544": [1], "951117010": [1], "951241870": [1], "951714181": [1], "951838825": [1], "952370915": [1], "952474243": [1], "952981766": [1], "953044970": [1], "953137977": [1], "953253967": [1], "953487989": [1], "953490107": [1], "953674320": [1], "954259671": [1], "954280086": [1], "954525007": [1], "954626985": [1], "954710948": [1], "954802631": [1], "955018779": [1], "955283549": [1], "955299371": [1], "955451406": [1], "955587147": [1], "956055956": [1], "956345343": [1], "956780457": [1], "956805671": [1], "957402676": [1], "957443240": [1], "957744781": [1], "957854914": [1], "957857802": [1], "958162436": [1], "958312482": [1], "958992973": [1], "959048888": [1], "959071605": [1], "959590297": [1], "959676970": [1], "959741308": [1], "959961201": [1], "960133298": [1], "960304770": [1], "960441357": [1], "960666163": [1], "960685585": [1], "961256602": [1], "961299100": [1], "961301307": [1], "961340923": [1], "961914889": [1], "961959337": [1], "962244254": [1], "962818507": [1], "963031485": [1], "963061928": [1], "963404411": [1], "963812284": [1], "964222752": [1], "964428165": [1], "964499709": [1], "964647844": [1], "964698266": [1], "964933540": [1], "965387917": [1], "965508894": [1], "965672665": [1], "965884376": [1], "965908494": [1], "966032692": [1], "966063091": [1], "966147129": [1], "966725465": [1], "966880332": [1], "966909276": [1], "967185235": [1], "967569048": [1], "967615691": [1], "967754017": [1], "968146916": [1], "968241196": [1], "968276128": [1], "968548905": [1], "968756476": [1], "968816463": [1], "968869402": [1], "969335372": [1], "969396601": [1], "969650911": [1], "969656210": [1], "969801720": [1], "969996636": [1], "970314937": [1], "970415704": [1], "970802453": [1], "970817240": [1], "971175167": [1], "971730552": [1], "972388009": [1], "973318462": [1], "973494647": [1], "973573888": [1], "974364520": [1], "974403971": [1], "975043726": [1], "975115858": [1], "975118031": [1], "975621683": [1], "975730421": [1], "975737393": [1], "975765982": [1], "975848256": [1], "976503847": [1], "976519794": [1], "976566553": [1], "976659894": [1], "976719110": [1], "977141603": [1], "977315245": [1], "977437241": [1], "977447015": [1], "977448777": [1], "977513509": [1], "977798631": [1], "977818547": [1], "977972425": [1], "978169124": [1], "978335746": [1], "978550729": [1], "979039943": [1], "979113270": [1], "979166043": [1], "979267797": [1], "979367437": [1], "979610967": [1], "980010607": [1], "980544652": [1], "980697947": [1], "980924318": [1], "981024428": [1], "981087316": [1], "981196981": [1], "981295835": [1], "981494918": [1], "981599875": [1], "981849756": [1], "982056286": [1], "982244983": [1], "982312106": [1], "982403346": [1], "982602841": [1], "982654955": [1], "982664133": [1], "982791147": [1], "982853950": [1], "983034236": [1], "983319577": [1], "983663840": [1], "984061593": [1], "984885189": [1], "985104549": [1], "985117123": [1], "985159252": [1], "985770733": [1], "985927310": [1], "986124261": [1], "986186455": [1], "986241184": [1], "986395005": [1], "986403109": [1], "986796049": [1], "986833768": [1], "986910810": [1], "987134942": [1], "987546315": [1], "987549715": [1], "987684155": [1], "987724032": [1], "988466668": [1], "988804556": [1], "989268580": [1], "989528116": [1], "989560954": [1], "990042915": [1], "990498451": [1], "990746755": [1], "990954446": [1], "991167316": [1], "991532887": [1], "991622130": [1], "992302690": [1], "992628437": [1], "993319277": [1], "993899521": [1], "994245700": [1], "994270321": [1], "994405106": [1], "994646080": [1], "994984235": [1], "995040173": [1], "995085251": [1], "995258438": [1], "995581613": [1], "995624836": [1], "995669039": [1], "995818747": [1], "995939344": [1], "996194924": [1], "996272183": [1], "996714803": [1], "997198239": [1], "997256303": [1], "997391425": [1], "997751197": [1], "997879342": [1], "998029632": [1], "998097780": [1], "998123036": [1], "998231766": [1], "998249909": [1], "998279016": [1], "998317397": [1], "998450541": [1], "998752638": [1], "999165378": [1], "999252300": [1], "999682150": [1], "999704320": [1], "999809974": [1], "1000079561": [1], "1000366900": [1], "1000571373": [1], "1000827620": [1], "1001011683": [1], "1001210108": [1], "1001503649": [1], "1002011849": [1], "1002189244": [1], "1002563995": [1], "1002697086": [1], "1002752266": [1], "1002786749": [1], "1002833158": [1], "1003076362": [1], "1003351052": [1], "1003527286": [1], "1003758904": [1], "1004486581": [1], "1004837010": [1], "1004916234": [1], "1005217243": [1], "1005364034": [1], "1005625269": [1], "1005844943": [1], "1005919962": [1], "1005962627": [1], "1006072365": [1], "1006139903": [1], "1006229148": [1], "1006348952": [1], "1006537327": [1], "1006665423": [1], "1006896575": [1], "1006926316": [1], "1006929683": [1], "1007108192": [1], "1007660887": [1], "1007681986": [1], "1007967966": [1], "1008034859": [1], "1008178843": [1], "1008415388": [1], "1008516488": [1], "1008637857": [1], "1008750195": [1], "1008819962": [1], "1008924384": [1], "1009268094": [1], "1009782721": [1], "1009958773": [1], "1010047646": [1], "1010918588": [1], "1011108133": [1], "1011116779": [1], "1011359929": [1], "1011634750": [1], "1012096578": [1], "1012860497": [1], "1012924543": [1], "1013107240": [1], "1013134551": [1], "1013656155": [1], "1014038394": [1], "1014128900": [1], "1014534298": [1], "1014633391": [1], "1014853913": [1], "1015022918": [1], "1015327850": [1], "1016270799": [1], "1016415776": [1], "1016479200": [1], "1016496489": [1], "1016539598": [1], "1016891393": [1], "1018070636": [1], "1018164695": [1], "1018165511": [1], "1018354841": [1], "1018435689": [1], "1018497572": [1], "1018888494": [1], "1018954738": [1], "1019415874": [1], "1019434959": [1], "1019673535": [1], "1019956044": [1], "1020527112": [1], "1021215205": [1], "1021854983": [1], "1021870062": [1], "1022085821": [1], "1022159013": [1], "1022852646": [1], "1023129724": [1], "1023498499": [1], "1023518731": [1], "1023830199": [1], "1023900315": [1], "1024003549": [1], "1024173300": [1], "1024450047": [1], "1024781192": [1], "1025610387": [1], "1025652801": [1], "1026475135": [1], "1026815889": [1], "1026933342": [1], "1027113773": [1], "1027482992": [1], "1027501958": [1], "1027527027": [1], "1027585441": [1], "1027667032": [1], "1028004658": [1], "1028076357": [1], "1028248260": [1], "1028251887": [1], "1028702830": [1], "1028975962": [1], "1029180681": [1], "1029383313": [1], "1029429555": [1], "1030043329": [1], "1030358470": [1], "1030407819": [1], "1030520202": [1], "1030795523": [1], "1030989918": [1], "1032110913": [1], "1032232585": [1], "1032767580": [1], "1032796929": [1], "1032813818": [1], "1033218370": [1], "1033373858": [1], "1033695276": [1], "1033818877": [1], "1034003475": [1], "1034291973": [1], "1035188742": [1], "1035854366": [1], "1036004499": [1], "1036086831": [1], "1036359076": [1], "1036508759": [1], "1036789743": [1], "1036842862": [1], "1036852806": [1], "1037185234": [1], "1037474923": [1], "1037716849": [1], "1038571609": [1], "1038852032": [1], "1039026172": [1], "1039110643": [1], "1039229148": [1], "1039316340": [1], "1039396388": [1], "1039708016": [1], "1040786256": [1], "1040839866": [1], "1040895866": [1], "1041201256": [1], "1041251080": [1], "1041403193": [1], "1041859516": [1], "1041975061": [1], "1042093419": [1], "1042111762": [1], "1042534892": [1], "1042742588": [1], "1043007717": [1], "1043167974": [1], "1043169427": [1], "1043183418": [1], "1043294316": [1], "1043948666": [1], "1044198164": [1], "1044853227": [1], "1045130985": [1], "1045363957": [1], "1045555803": [1], "1045667160": [1], "1045802619": [1], "1046014014": [1], "1046132549": [1], "1046360241": [1], "1047185952": [1], "1047320657": [1], "1047557699": [1], "1047573233": [1], "1047596960": [1], "1048412282": [1], "1048468808": [1], "1048938275": [1], "1048950484": [1], "1049546274": [1], "1049587019": [1], "1049874144": [1], "1050100265": [1], "1050432509": [1], "1050478771": [1], "1050802655": [1], "1051040440": [1], "1051946328": [1], "1052380750": [1], "1052459761": [1], "1052794649": [1], "1053611574": [1], "1053682131": [1], "1053721972": [1], "1053817294": [1], "1053832810": [1], "1053890379": [1], "1054104729": [1], "1054174289": [1], "1054317911": [1], "1054710619": [1], "1054886738": [1], "1055370419": [1], "1055385375": [1], "1055411886": [1], "1055492225": [1], "1055895040": [1], "1056363928": [1], "1056553688": [1], "1056866320": [1], "1057070677": [1], "1057240205": [1], "1057376023": [1], "1057601590": [1], "1058297435": [1], "1058542921": [1], "1059045823": [1], "1059426804": [1], "1059431384": [1], "1059452982": [1], "1059571518": [1], "1060103327": [1], "1060202981": [1], "1060341291": [1], "1060923444": [1], "1061258934": [1], "1061517690": [1], "1061825913": [1], "1061831044": [1], "1062373877": [1], "1062944418": [1], "1063029466": [1], "1063365622": [1], "1063367088": [1], "1063974508": [1], "1064235646": [1], "1064321894": [1], "1064404393": [1], "1064588366": [1], "1064733223": [1], "1065087848": [1], "1065423459": [1], "1065430484": [1], "1066090930": [1], "1066172740": [1], "1066407686": [1], "1066435785": [1], "1066572859": [1], "1066720090": [1], "1066864807": [1], "1067009528": [1], "1067154864": [1], "1067422557": [1], "1067681587": [1], "1067702270": [1], "1068519399": [1], "1068546185": [1], "1068634919": [1], "1069053808": [1], "1069448304": [1], "1069670921": [1], "1069988909": [1], "1070164374": [1], "1070394782": [1], "1070807954": [1], "1071002338": [1], "1071081588": [1], "1071158098": [1], "1071325624": [1], "1071568326": [1], "1071694575": [1], "1071716888": [1], "1071964165": [1], "1072242024": [1], "1072577361": [1], "1072714154": [1], "1072727465": [1], "1072762008": [1], "1072844722": [1], "1073094114": [1], "1073486259": [1], "1073577626": [1], "1073642709": [1], "1073850611": [1], "1073905007": [1], "1074187281": [1], "1074861290": [1], "1074880973": [1], "1074986451": [1], "1075053359": [1], "1075296279": [1], "1075704259": [1], "1075709296": [1], "1076055667": [1], "1076923665": [1], "1076951673": [1], "1076984532": [1], "1077356550": [1], "1077550483": [1], "1077665522": [1], "1077862741": [1], "1078191387": [1], "1078270481": [1], "1078719785": [1], "1078750156": [1], "1079438170": [1], "1079617885": [1], "1080387837": [1], "1080391363": [1], "1080951863": [1], "1081366530": [1], "1081591675": [1], "1081713033": [1], "1081966337": [1], "1082047295": [1], "1082082955": [1], "1082256683": [1], "1082430176": [1], "1082487957": [1], "1082607356": [1], "1083158706": [1], "1083300464": [1], "1083564081": [1], "1083680163": [1], "1084135364": [1], "1084437481": [1], "1084514756": [1], "1084609317": [1], "1084866026": [1], "1084898066": [1], "1085042712": [1], "1085047505": [1], "1085092613": [1], "1085240074": [1], "1085367019": [1], "1085552078": [1], "1085986160": [1], "1086051996": [1], "1086127892": [1], "1086138570": [1], "1086539892": [1], "1086543603": [1], "1086662834": [1], "1086870704": [1], "1087286626": [1], "1087810447": [1], "1088372936": [1], "1088402394": [1], "1088542916": [1], "1089754870": [1], "1089804773": [1], "1090004336": [1], "1090144307": [1], "1090198947": [1], "1090511012": [1], "1090513568": [1], "1090774860": [1], "1090793131": [1], "1090815246": [1], "1091061182": [1], "1091112664": [1], "1091580324": [1], "1091847577": [1], "1091861486": [1], "1091929792": [1], "1092029809": [1], "1092090629": [1], "1092221482": [1], "1092275711": [1], "1092284902": [1], "1092559027": [1], "1092821091": [1], "1093359474": [1], "1093638083": [1], "1094078537": [1], "1094124038": [1], "1094262958": [1], "1094304817": [1], "1094431188": [1], "1095110812": [1], "1095443414": [1], "1095726424": [1], "1096254857": [1], "1096749154": [1], "1097411397": [1], "1098006172": [1], "1098025108": [1], "1098411222": [1], "1099216362": [1], "1099439025": [1], "1099788543": [1], "1099822835": [1], "1100443036": [1], "1100491100": [1], "1100679939": [1], "1101113163": [1], "1101128614": [1], "1101409782": [1], "1101524512": [1], "1101683044": [1], "1101783995": [1], "1101858389": [1], "1102007971": [1], "1102311749": [1], "1102730669": [1], "1103132892": [1], "1103475917": [1], "1103525755": [1], "1103839741": [1], "1104126552": [1], "1104672952": [1], "1105203605": [1], "1105486629": [1], "1105509475": [1], "1105598992": [1], "1105732553": [1], "1105848254": [1], "1105925468": [1], "1105982683": [1], "1106304142": [1], "1106819729": [1], "1107643615": [1], "1108056308": [1], "1108412745": [1], "1108596720": [1], "1108695618": [1], "1108966078": [1], "1109086065": [1], "1109127146": [1], "1109239254": [1], "1109423006": [1], "1109484233": [1], "1109795113": [1], "1110164711": [1], "1110514725": [1], "1110911870": [1], "1111279210": [1], "1111817787": [1], "1112041602": [1], "1112729766": [1], "1113045537": [1], "1113305234": [1], "1113445507": [1], "1113707859": [1], "1114137802": [1], "1114458522": [1], "1114644625": [1], "1115030399": [1], "1115059464": [1], "1115421882": [1], "1115906954": [1], "1116126552": [1], "1116999251": [1], "1117145160": [1], "1117373297": [1], "1118075767": [1], "1118293063": [1], "1119526310": [1], "1120029658": [1], "1120147373": [1], "1120251456": [1], "1120341802": [1], "1120548371": [1], "1120575524": [1], "1120985717": [1], "1121048289": [1], "1121159469": [1], "1121220482": [1], "1121408892": [1], "1121617380": [1], "1121791461": [1], "1121960773": [1], "1122359423": [1], "1122878866": [1], "1122924898": [1], "1123917069": [1], "1124119989": [1], "1124350769": [1], "1124352927": [1], "1124483266": [1], "1124837929": [1], "1124929490": [1], "1125706064": [1], "1125706743": [1], "1125976033": [1], "1125999877": [1], "1126160258": [1], "1126192158": [1], "1126584231": [1], "1127198036": [1], "1127235794": [1], "1127588055": [1], "1127611289": [1], "1127879418": [1], "1127950315": [1], "1128005036": [1], "1128020377": [1], "1128085233": [1], "1128176761": [1], "1128226802": [1], "1128376934": [1], "1128754909": [1], "1129431690": [1], "1129458081": [1], "1129987063": [1], "1130307376": [1], "1130356053": [1], "1130365150": [1], "1130401014": [1], "1130563068": [1], "1131502184": [1], "1132417976": [1], "1133236642": [1], "1133649346": [1], "1133832653": [1], "1133929612": [1], "1133929953": [1], "1133932738": [1], "1133947675": [1], "1134355480": [1], "1134609904": [1], "1134623655": [1], "1134920263": [1], "1135538986": [1], "1135560302": [1], "1136134328": [1], "1136234726": [1], "1136366033": [1], "1137083345": [1], "1137112223": [1], "1137396776": [1], "1138201305": [1], "1138385639": [1], "1138552385": [1], "1138776284": [1], "1138874674": [1], "1138887058": [1], "1138937556": [1], "1139562690": [1], "1140034792": [1], "1140724723": [1], "1140943039": [1], "1140957924": [1], "1141183680": [1], "1141293413": [1], "1141793120": [1], "1142240607": [1], "1142867872": [1], "1143006264": [1], "1143650233": [1], "1143917535": [1], "1144838783": [1], "1144906516": [1], "1145318260": [1], "1145809025": [1], "1145907516": [1], "1146218866": [1], "1146400146": [1], "1146632053": [1], "1146733037": [1], "1147005733": [1], "1147181774": [1], "1147626491": [1], "1147818272": [1], "1148016917": [1], "1148515517": [1], "1148667308": [1], "1148874283": [1], "1149345974": [1], "1149774048": [1], "1149991512": [1], "1150111451": [1], "1150166839": [1], "1150371362": [1], "1150928862": [1], "1151652849": [1], "1151662491": [1], "1152279649": [1], "1152548584": [1], "1152648359": [1], "1152820145": [1], "1152872626": [1], "1152878889": [1], "1153127135": [1], "1153305010": [1], "1153338228": [1], "1153616180": [1], "1154324913": [1], "1154893414": [1], "1155447253": [1], "1155614019": [1], "1155660685": [1], "1155848561": [1], "1156260982": [1], "1156439637": [1], "1157147916": [1], "1157343239": [1], "1157985367": [1], "1158084031": [1], "1158156131": [1], "1158268716": [1], "1158750531": [1], "1158853874": [1], "1159335315": [1], "1159360321": [1], "1160033284": [1], "1160481108": [1], "1160667608": [1], "1161213974": [1], "1161301381": [1], "1161868820": [1], "1162067450": [1], "1162258249": [1], "1162408506": [1], "1162563649": [1], "1162924054": [1], "1162955570": [1], "1163165927": [1], "1163297506": [1], "1163412535": [1], "1163417925": [1], "1163592522": [1], "1163874341": [1], "1163932395": [1], "1164227915": [1], "1164321550": [1], "1165062755": [1], "1165083448": [1], "1165091766": [1], "1165371966": [1], "1165714501": [1], "1166044593": [1], "1166292473": [1], "1166344748": [1], "1166499173": [1], "1166913144": [1], "1167383361": [1], "1167411416": [1], "1167413233": [1], "1167481708": [1], "1167518761": [1], "1167609515": [1], "1167645761": [1], "1167690169": [1], "1167721958": [1], "1168292217": [1], "1168509664": [1], "1169410445": [1], "1169612907": [1], "1169663922": [1], "1170586927": [1], "1170721286": [1], "1171192346": [1], "1171467194": [1], "1171553141": [1], "1171587200": [1], "1171669309": [1], "1171714917": [1], "1172018725": [1], "1172281501": [1], "1172338452": [1], "1173559083": [1], "1173945357": [1], "1174101221": [1], "1174123826": [1], "1174172999": [1], "1174198380": [1], "1174373876": [1], "1174457560": [1], "1174774517": [1], "1174849406": [1], "1175059681": [1], "1175129762": [1], "1175239486": [1], "1175263674": [1], "1175293043": [1], "1175648787": [1], "1175842439": [1], "1175905034": [1], "1176023661": [1], "1176239963": [1], "1176439626": [1], "1176461012": [1], "1176685421": [1], "1176924145": [1], "1177039856": [1], "1177153418": [1], "1177459106": [1], "1177754436": [1], "1177818806": [1], "1177933451": [1], "1177982385": [1], "1178025166": [1], "1178180329": [1], "1178315474": [1], "1178327659": [1], "1178703414": [1], "1178763929": [1], "1178783515": [1], "1178972195": [1], "1179189479": [1], "1179222963": [1], "1179539186": [1], "1180064619": [1], "1180161259": [1], "1180190279": [1], "1180450723": [1], "1180880438": [1], "1181301668": [1], "1181470216": [1], "1181473360": [1], "1182902045": [1], "1183100507": [1], "1183149887": [1], "1183371708": [1], "1183382516": [1], "1183726071": [1], "1183932024": [1], "1184076212": [1], "1184254161": [1], "1184349400": [1], "1184416841": [1], "1184609538": [1], "1185111714": [1], "1185274227": [1], "1185275189": [1], "1185447772": [1], "1185567215": [1], "1185783226": [1], "1186033025": [1], "1186650216": [1], "1186675843": [1], "1186785326": [1], "1186843233": [1], "1187079949": [1], "1187185908": [1], "1188030493": [1], "1188082812": [1], "1188633905": [1], "1188649791": [1], "1188924100": [1], "1189584525": [1], "1189614449": [1], "1189770285": [1], "1190184536": [1], "1190188725": [1], "1190391274": [1], "1190645300": [1], "1190768226": [1], "1191820849": [1], "1191824714": [1], "1191971796": [1], "1191993250": [1], "1192123157": [1], "1192256680": [1], "1192256922": [1], "1192261312": [1], "1192393299": [1], "1192763703": [1], "1193331796": [1], "1193726951": [1], "1193891464": [1], "1194971903": [1], "1195010866": [1], "1195249115": [1], "1196054610": [1], "1196361742": [1], "1196564717": [1], "1196620137": [1], "1196652219": [1], "1197338955": [1], "1197385605": [1], "1197425816": [1], "1197774278": [1], "1197780336": [1], "1198123089": [1], "1198496388": [1], "1198617412": [1], "1198709847": [1], "1199375832": [1], "1199465038": [1], "1199628898": [1], "1199676181": [1], "1200481655": [1], "1200900412": [1], "1201394151": [1], "1202267731": [1], "1202271376": [1], "1202497689": [1], "1203646552": [1], "1203668741": [1], "1203914949": [1], "1204437756": [1], "1204482404": [1], "1204652313": [1], "1204984581": [1], "1205696246": [1], "1205849019": [1], "1205897795": [1], "1206031112": [1], "1206192623": [1], "1207009782": [1], "1207289939": [1], "1207988017": [1], "1208028910": [1], "1208078876": [1], "1209536917": [1], "1209551549": [1], "1209852058": [1], "1210034958": [1], "1210138494": [1], "1210232881": [1], "1210281399": [1], "1210374787": [1], "1210938234": [1], "1211626880": [1], "1212164032": [1], "1212227798": [1], "1212439983": [1], "1212560936": [1], "1212866093": [1], "1213157352": [1], "1213384074": [1], "1213530995": [1], "1213584947": [1], "1213648582": [1], "1214277573": [1], "1214347702": [1], "1214464240": [1], "1214500054": [1], "1214551033": [1], "1214600036": [1], "1214920884": [1], "1215031844": [1], "1215217158": [1], "1215479155": [1], "1215912623": [1], "1216059966": [1], "1216264332": [1], "1216585378": [1], "1216715205": [1], "1216791233": [1], "1217002448": [1], "1217314520": [1], "1217545573": [1], "1217791525": [1], "1217915740": [1], "1217948228": [1], "1218020918": [1], "1218111620": [1], "1218159114": [1], "1218415929": [1], "1218488460": [1], "1218564544": [1], "1218732467": [1], "1219176229": [1], "1219256843": [1], "1219504459": [1], "1219662173": [1], "1219738967": [1], "1219757692": [1], "1220063863": [1], "1220390201": [1], "1220639951": [1], "1221099816": [1], "1221277746": [1], "1221666148": [1], "1222134366": [1], "1222493520": [1], "1222609499": [1], "1223536081": [1], "1224080658": [1], "1224334167": [1], "1224777151": [1], "1225851340": [1], "1226194696": [1], "1226298600": [1], "1226659924": [1], "1226743179": [1], "1226778119": [1], "1226849292": [1], "1227234571": [1], "1227274959": [1], "1227641001": [1], "1227816114": [1], "1228069468": [1], "1228428440": [1], "1228430120": [1], "1228739497": [1], "1229229785": [1], "1229486746": [1], "1229581220": [1], "1229683900": [1], "1229796794": [1], "1230055736": [1], "1230179076": [1], "1230690446": [1], "1230802814": [1], "1230896358": [1], "1231328293": [1], "1231596722": [1], "1231956922": [1], "1232147510": [1], "1232405995": [1], "1232574933": [1], "1232728840": [1], "1232970776": [1], "1233232829": [1], "1233388017": [1], "1233399822": [1], "1234138010": [1], "1234157267": [1], "1234292103": [1], "1234693119": [1], "1235086931": [1], "1235141099": [1], "1235290894": [1], "1235610423": [1], "1235619554": [1], "1235697419": [1], "1235839001": [1], "1236411833": [1], "1237141884": [1], "1237338305": [1], "1237610404": [1], "1237662341": [1], "1237815116": [1], "1238563307": [1], "1238879111": [1], "1238881495": [1], "1238901933": [1], "1239084984": [1], "1239189160": [1], "1239248389": [1], "1239499085": [1], "1239555060": [1], "1239800097": [1], "1239901476": [1], "1239904956": [1], "1240010385": [1], "1240118481": [1], "1240993505": [1], "1241448615": [1], "1242059726": [1], "1242308422": [1], "1242405483": [1], "1242564725": [1], "1242597041": [1], "1242938411": [1], "1243015611": [1], "1243149971": [1], "1243166013": [1], "1243170529": [1], "1243374992": [1], "1243541186": [1], "1244153079": [1], "1244306767": [1], "1244476084": [1], "1244641145": [1], "1244753781": [1], "1244813697": [1], "1244870296": [1], "1245100900": [1], "1245356301": [1], "1245410477": [1], "1245550866": [1], "1245652329": [1], "1245712890": [1], "1245937622": [1], "1246148040": [1], "1246164989": [1], "1246185713": [1], "1246507704": [1], "1246528924": [1], "1247367725": [1], "1247499975": [1], "1247660611": [1], "1247868627": [1], "1248484450": [1], "1248661794": [1], "1248842981": [1], "1249120970": [1], "1249302948": [1], "1249588457": [1], "1249596757": [1], "1249599168": [1], "1249639591": [1], "1250344196": [1], "1250380110": [1], "1250407248": [1], "1250430567": [1], "1250484477": [1], "1250642439": [1], "1250724163": [1], "1250801339": [1], "1250846862": [1], "1250983260": [1], "1251295876": [1], "1251761741": [1], "1251976981": [1], "1252032466": [1], "1252442684": [1], "1252529591": [1], "1252854485": [1], "1253452141": [1], "1253484239": [1], "1253521694": [1], "1254605636": [1], "1255247880": [1], "1255653107": [1], "1256108398": [1], "1256367707": [1], "1256396373": [1], "1256582540": [1], "1256765535": [1], "1257056898": [1], "1257199362": [1], "1257327898": [1], "1257403684": [1], "1257922229": [1], "1257925827": [1], "1257975516": [1], "1258039171": [1], "1258217038": [1], "1258229639": [1], "1258410394": [1], "1258841077": [1], "1259486339": [1], "1259529331": [1], "1260255645": [1], "1260420285": [1], "1261295872": [1], "1261345370": [1], "1261473355": [1], "1261477492": [1], "1261719277": [1], "1261719527": [1], "1262376088": [1], "1262700658": [1], "1263253853": [1], "1263483513": [1], "1264152828": [1], "1264494418": [1], "1264652808": [1], "1265121511": [1], "1265350763": [1], "1265490277": [1], "1265852257": [1], "1265909994": [1], "1266253619": [1], "1266358035": [1], "1267364652": [1], "1267733249": [1], "1267792449": [1], "1267869371": [1], "1268018882": [1], "1268508876": [1], "1268754947": [1], "1269664307": [1], "1269822177": [1], "1270304570": [1], "1270305594": [1], "1271160483": [1], "1272566724": [1], "1272611315": [1], "1272993144": [1], "1273427274": [1], "1273469673": [1], "1273719045": [1], "1273934596": [1], "1273939171": [1], "1273965722": [1], "1274436732": [1], "1274454102": [1], "1274789696": [1], "1275017314": [1], "1275427444": [1], "1275495415": [1], "1275790906": [1], "1276022250": [1], "1276109587": [1], "1276636924": [1], "1276858632": [1], "1277256893": [1], "1277858674": [1], "1278313358": [1], "1278430882": [1], "1278550928": [1], "1278634435": [1], "1278822073": [1], "1279106246": [1], "1279231995": [1], "1279650561": [1], "1279837838": [1], "1279985497": [1], "1280008745": [1], "1280030740": [1], "1280090560": [1], "1280369982": [1], "1280504849": [1], "1280634126": [1], "1281053846": [1], "1281162510": [1], "1281422981": [1], "1281676671": [1], "1281933780": [1], "1282179637": [1], "1282355042": [1], "1283114530": [1], "1283732064": [1], "1284204014": [1], "1284345986": [1], "1284437559": [1], "1285077980": [1], "1285444005": [1], "1285938725": [1], "1286214548": [1], "1286249407": [1], "1287076252": [1], "1287252763": [1], "1288139639": [1], "1288144775": [1], "1289457915": [1], "1289638016": [1], "1289876180": [1], "1290447610": [1], "1290530912": [1], "1290738887": [1], "1290787892": [1], "1290790727": [1], "1291135356": [1], "1291447032": [1], "1291838308": [1], "1292077123": [1], "1292078581": [1], "1292176875": [1], "1292415476": [1], "1292615001": [1], "1292879692": [1], "1292912368": [1], "1293000174": [1], "1293007202": [1], "1293011057": [1], "1293442369": [1], "1293442646": [1], "1293474660": [1], "1293536134": [1], "1293767009": [1], "1293778919": [1], "1293847124": [1], "1293878517": [1], "1294214116": [1], "1294347454": [1], "1294503600": [1], "1295372141": [1], "1296730697": [1], "1297249895": [1], "1297412606": [1], "1297956062": [1], "1298237582": [1], "1298477357": [1], "1298722447": [1], "1298955552": [1], "1299309895": [1], "1299368855": [1], "1299420130": [1], "1299662240": [1], "1299705044": [1], "1299903033": [1], "1300034957": [1], "1300439508": [1], "1300441713": [1], "1300984642": [1], "1301269925": [1], "1301439053": [1], "1301867598": [1], "1301911729": [1], "1302079850": [1], "1302360174": [1], "1302410658": [1], "1302743992": [1], "1302775098": [1], "1303393831": [1], "1303458196": [1], "1303698172": [1], "1304767080": [1], "1304786244": [1], "1305135710": [1], "1305688463": [1], "1305931985": [1], "1305933193": [1], "1306261144": [1], "1306607638": [1], "1307303483": [1], "1307892961": [1], "1307901484": [1], "1308028220": [1], "1308550599": [1], "1308737620": [1], "1308789697": [1], "1309500056": [1], "1309515311": [1], "1309648045": [1], "1309751717": [1], "1309853891": [1], "1309918749": [1], "1310141162": [1], "1310221405": [1], "1311827589": [1], "1311911997": [1], "1312142288": [1], "1312657585": [1], "1312699968": [1], "1312781661": [1], "1312819439": [1], "1312862529": [1], "1313068140": [1], "1313114431": [1], "1313328787": [1], "1313343439": [1], "1313415139": [1], "1313619400": [1], "1314208068": [1], "1314516438": [1], "1314894113": [1], "1314903495": [1], "1314991033": [1], "1315074037": [1], "1315190231": [1], "1315556444": [1], "1315675228": [1], "1315884105": [1], "1316214682": [1], "1316563708": [1], "1316935190": [1], "1317696494": [1], "1317808195": [1], "1317964455": [1], "1318009131": [1], "1318387045": [1], "1318484974": [1], "1318816358": [1], "1318852449": [1], "1318956121": [1], "1319848907": [1], "1319921099": [1], "1320046176": [1], "1320185086": [1], "1320278084": [1], "1320295119": [1], "1320372914": [1], "1320577109": [1], "1320885236": [1], "1320968680": [1], "1322132192": [1], "1322198190": [1], "1322488212": [1], "1322581527": [1], "1322634862": [1], "1323269637": [1], "1323742518": [1], "1323950066": [1], "1324151221": [1], "1324406999": [1], "1324654425": [1], "1325493047": [1], "1325698341": [1], "1326021146": [1], "1326172372": [1], "1326362470": [1], "1326449086": [1], "1326692597": [1], "1326758989": [1], "1326916979": [1], "1327047162": [1], "1327162078": [1], "1327172841": [1], "1327258200": [1], "1327796688": [1], "1328012679": [1], "1328115985": [1], "1329131057": [1], "1329131254": [1], "1329509829": [1], "1329791375": [1], "1330062390": [1], "1330070783": [1], "1330276682": [1], "1330279586": [1], "1330375794": [1], "1330501765": [1], "1330548103": [1], "1330551755": [1], "1330635616": [1], "1330766700": [1], "1331193456": [1], "1331414303": [1], "1331460033": [1], "1331804365": [1], "1332050488": [1], "1332168057": [1], "1332360162": [1], "1332389761": [1], "1332416726": [1], "1333133516": [1], "1333524057": [1], "1333581265": [1], "1333705673": [1], "1333742860": [1], "1333959746": [1], "1333995893": [1], "1334101455": [1], "1334141070": [1], "1334369155": [1], "1334695463": [1], "1335007348": [1], "1335048390": [1], "1335286925": [1], "1335334977": [1], "1335505674": [1], "1335754958": [1], "1336530418": [1], "1336883668": [1], "1337075215": [1], "1337551375": [1], "1337607239": [1], "1337615107": [1], "1337852202": [1], "1338015532": [1], "1338086415": [1], "1338281136": [1], "1338356700": [1], "1338603553": [1], "1339127260": [1], "1339195070": [1], "1339444651": [1], "1339515559": [1], "1339594101": [1], "1339642975": [1], "1339893342": [1], "1339953828": [1], "1339999622": [1], "1340243249": [1], "1340403927": [1], "1340458480": [1], "1340641658": [1], "1340826070": [1], "1341105275": [1], "1341221384": [1], "1341612616": [1], "1341774752": [1], "1341944224": [1], "1342418723": [1], "1342561631": [1], "1343005677": [1], "1343398352": [1], "1343494260": [1], "1343531069": [1], "1343684325": [1], "1343761612": [1], "1343877389": [1], "1344003398": [1], "1344303202": [1], "1344352857": [1], "1344459448": [1], "1344541128": [1], "1344777859": [1], "1344870691": [1], "1345373118": [1], "1345568585": [1], "1345672716": [1], "1346054889": [1], "1346390737": [1], "1346404759": [1], "1346521968": [1], "1346662559": [1], "1347708231": [1], "1347908360": [1], "1347935617": [1], "1348007203": [1], "1348054797": [1], "1348098500": [1], "1348296204": [1], "1348392001": [1], "1348564405": [1], "1348674171": [1], "1349529703": [1], "1349686826": [1], "1350591224": [1], "1351160235": [1], "1351179828": [1], "1351257483": [1], "1351675131": [1], "1351988563": [1], "1352202118": [1], "1352240309": [1], "1352256026": [1], "1352265526": [1], "1352546001": [1], "1352555260": [1], "1352766880": [1], "1352794437": [1], "1352844553": [1], "1352869123": [1], "1353071937": [1], "1353161474": [1], "1353175615": [1], "1353189791": [1], "1353346786": [1], "1353440753": [1], "1353470153": [1], "1353929766": [1], "1354103491": [1], "1354438664": [1], "1354737901": [1], "1354774536": [1], "1354817356": [1], "1354884406": [1], "1354930950": [1], "1355013093": [1], "1355040462": [1], "1355170581": [1], "1355509970": [1], "1355593108": [1], "1355967751": [1], "1356086874": [1], "1356346854": [1], "1357225652": [1], "1358176253": [1], "1358558861": [1], "1358891899": [1], "1358957540": [1], "1360361572": [1], "1360459443": [1], "1360858890": [1], "1361080832": [1], "1361186278": [1], "1361187581": [1], "1361330862": [1], "1361445292": [1], "1361513582": [1], "1361718493": [1], "1362015040": [1], "1362155931": [1], "1362413365": [1], "1362448931": [1], "1362563744": [1], "1362844596": [1], "1363011200": [1], "1363273204": [1], "1363704516": [1], "1363767286": [1], "1363821202": [1], "1364610551": [1], "1364749312": [1], "1364913191": [1], "1365090511": [1], "1365176703": [1], "1365509765": [1], "1366521432": [1], "1366627284": [1], "1366826469": [1], "1366889907": [1], "1367093196": [1], "1367387460": [1], "1367458644": [1], "1367687680": [1], "1367707795": [1], "1367903141": [1], "1368319841": [1], "1368443920": [1], "1368479513": [1], "1368750444": [1], "1368811011": [1], "1368832888": [1], "1368862838": [1], "1368936931": [1], "1369005321": [1], "1369256523": [1], "1369258007": [1], "1369500131": [1], "1369972072": [1], "1370154113": [1], "1370281264": [1], "1370480888": [1], "1370560346": [1], "1370779824": [1], "1371347448": [1], "1371898831": [1], "1372403802": [1], "1372822274": [1], "1372831157": [1], "1372862150": [1], "1373045189": [1], "1373159617": [1], "1373315176": [1], "1373407953": [1], "1373509485": [1], "1373887438": [1], "1374203139": [1], "1374363696": [1], "1375376548": [1], "1375540857": [1], "1375639267": [1], "1375799833": [1], "1375851437": [1], "1376073999": [1], "1376095087": [1], "1376436980": [1], "1377002214": [1], "1377226402": [1], "1377372974": [1], "1377625349": [1], "1377987019": [1], "1378031950": [1], "1378503939": [1], "1378594409": [1], "1378728326": [1], "1378848721": [1], "1378857256": [1], "1378865674": [1], "1379097653": [1], "1379179859": [1], "1379753881": [1], "1379868543": [1], "1380196028": [1], "1380206320": [1], "1380311518": [1], "1380409888": [1], "1380557646": [1], "1380674624": [1], "1380776880": [1], "1380790524": [1], "1380881866": [1], "1380887411": [1], "1380941081": [1], "1381193097": [1], "1381370000": [1], "1381646672": [1], "1381723255": [1], "1382162659": [1], "1382523505": [1], "1383140537": [1], "1383363421": [1], "1383538361": [1], "1383563046": [1], "1384067275": [1], "1384264450": [1], "1384865583": [1], "1385002580": [1], "1385148306": [1], "1385489801": [1], "1385514761": [1], "1386061901": [1], "1386091981": [1], "1386356749": [1], "1386366741": [1], "1386580740": [1], "1386772572": [1], "1387793656": [1], "1387967847": [1], "1387998139": [1], "1388634679": [1], "1388726926": [1], "1388966286": [1], "1389461494": [1], "1389697844": [1], "1390609201": [1], "1391077646": [1], "1391233759": [1], "1391543276": [1], "1391750031": [1], "1391894544": [1], "1392101103": [1], "1392199392": [1], "1392249924": [1], "1392317999": [1], "1392781239": [1], "1392999159": [1], "1393132628": [1], "1393765233": [1], "1393819552": [1], "1394557191": [1], "1394560839": [1], "1395147252": [1], "1395197445": [1], "1395401085": [1], "1395543349": [1], "1395570929": [1], "1395645842": [1], "1395762993": [1], "1395883176": [1], "1395987754": [1], "1396231710": [1], "1396240216": [1], "1396375962": [1], "1396464744": [1], "1396997760": [1], "1397032275": [1], "1397111997": [1], "1397241311": [1], "1397359913": [1], "1397430420": [1], "1397613259": [1], "1398098861": [1], "1398207070": [1], "1398238619": [1], "1398262376": [1], "1398308508": [1], "1398912883": [1], "1398973475": [1], "1398982141": [1], "1399029940": [1], "1399057653": [1], "1399057671": [1], "1399146791": [1], "1399428502": [1], "1399499960": [1], "1399743541": [1], "1400383523": [1], "1400551973": [1], "1400656177": [1], "1400711770": [1], "1400722816": [1], "1401128133": [1], "1401174422": [1], "1402015611": [1], "1402067942": [1], "1402412825": [1], "1402432688": [1], "1402491103": [1], "1402506597": [1], "1402541471": [1], "1402837346": [1], "1402982711": [1], "1403116368": [1], "1403170174": [1], "1403237524": [1], "1403572288": [1], "1403663366": [1], "1404148597": [1], "1405091221": [1], "1405101695": [1], "1406083215": [1], "1406201601": [1], "1406656796": [1], "1406964056": [1], "1407654053": [1], "1407685778": [1], "1407743714": [1], "1407827616": [1], "1408233514": [1], "1408287430": [1], "1408636621": [1], "1408641385": [1], "1408908545": [1], "1408940236": [1], "1409257175": [1], "1409428875": [1], "1409449183": [1], "1410054382": [1], "1410101551": [1], "1410576289": [1], "1410781344": [1], "1411222604": [1], "1411352436": [1], "1411601918": [1], "1411827997": [1], "1411948651": [1], "1411971348": [1], "1411975864": [1], "1411989557": [1], "1412327764": [1], "1412469207": [1], "1413255479": [1], "1413521023": [1], "1413658446": [1], "1413664965": [1], "1414769306": [1], "1415093777": [1], "1415266487": [1], "1415976044": [1], "1416309501": [1], "1416438387": [1], "1416630935": [1], "1417110645": [1], "1417677244": [1], "1418070855": [1], "1418199341": [1], "1418288207": [1], "1418773612": [1], "1418837712": [1], "1419549623": [1], "1419738436": [1], "1420073309": [1], "1420155686": [1], "1420353151": [1], "1420547864": [1], "1420639184": [1], "1421170769": [1], "1421768607": [1], "1422207314": [1], "1422812079": [1], "1423105517": [1], "1423971761": [1], "1424102750": [1], "1424169811": [1], "1424782279": [1], "1425011632": [1], "1425089242": [1], "1425771810": [1], "1425955732": [1], "1426089337": [1], "1426148715": [1], "1426300334": [1], "1426614555": [1], "1426980536": [1], "1427031934": [1], "1427121002": [1], "1427186177": [1], "1428306470": [1], "1428337586": [1], "1428468162": [1], "1428560688": [1], "1428758912": [1], "1428798527": [1], "1429776562": [1], "1430541307": [1], "1430585267": [1], "1430668416": [1], "1430874369": [1], "1430939624": [1], "1431244561": [1], "1431428746": [1], "1431495765": [1], "1431694559": [1], "1431799639": [1], "1432028930": [1], "1432121281": [1], "1432251789": [1], "1432297370": [1], "1432478090": [1], "1432641140": [1], "1432785306": [1], "1433271209": [1], "1433332147": [1], "1433429459": [1], "1433749477": [1], "1433750046": [1], "1434146829": [1], "1434175399": [1], "1435296749": [1], "1436065841": [1], "1436306619": [1], "1436648672": [1], "1437090524": [1], "1437232780": [1], "1437885484": [1], "1437889940": [1], "1438196514": [1], "1438488221": [1], "1438847036": [1], "1438863914": [1], "1439123190": [1], "1439328252": [1], "1439540445": [1], "1439719967": [1], "1439902415": [1], "1439973396": [1], "1439978342": [1], "1440237428": [1], "1440815963": [1], "1441085585": [1], "1441141242": [1], "1441744222": [1], "1441797725": [1], "1441799755": [1], "1442713364": [1], "1442754138": [1], "1442991058": [1], "1443017916": [1], "1443045195": [1], "1443113409": [1], "1443211328": [1], "1443300153": [1], "1443358923": [1], "1444057233": [1], "1444310958": [1], "1444516404": [1], "1444860667": [1], "1445069843": [1], "1445135559": [1], "1445215368": [1], "1445302456": [1], "1445709625": [1], "1445793236": [1], "1446391871": [1], "1446555329": [1], "1446602410": [1], "1446703673": [1], "1447229497": [1], "1447950771": [1], "1448359412": [1], "1448932984": [1], "1449168045": [1], "1449305339": [1], "1449446200": [1], "1449632269": [1], "1449716355": [1], "1449749130": [1], "1449782656": [1], "1449933174": [1], "1450178777": [1], "1450447737": [1], "1450510618": [1], "1451201888": [1], "1451253443": [1], "1451832189": [1], "1451953100": [1], "1452102676": [1], "1452395386": [1], "1452428299": [1], "1452710458": [1], "1454739717": [1], "1455102106": [1], "1455417175": [1], "1455587555": [1], "1456052183": [1], "1456645739": [1], "1456858440": [1], "1457223342": [1], "1457419905": [1], "1458053946": [1], "1458362984": [1], "1459455639": [1], "1460034363": [1], "1460167757": [1], "1460383794": [1], "1460559857": [1], "1460696746": [1], "1461174153": [1], "1461210143": [1], "1461265108": [1], "1461663750": [1], "1461756184": [1], "1461775631": [1], "1461911807": [1], "1462149813": [1], "1462337840": [1], "1462650226": [1], "1462803006": [1], "1462843170": [1], "1463151427": [1], "1463250617": [1], "1463327585": [1], "1463806439": [1], "1463814328": [1], "1463931957": [1], "1464277155": [1], "1464293171": [1], "1464302705": [1], "1464376020": [1], "1464563190": [1], "1464914027": [1], "1464987738": [1], "1465004643": [1], "1465035319": [1], "1465114911": [1], "1465256909": [1], "1465619971": [1], "1466186300": [1], "1466275996": [1], "1466620592": [1], "1467928094": [1], "1468165357": [1], "1468449593": [1], "1468521117": [1], "1468609280": [1], "1468691886": [1], "1469178544": [1], "1469396960": [1], "1469847051": [1], "1469930617": [1], "1470218457": [1], "1470449882": [1], "1471008591": [1], "1471152174": [1], "1471205682": [1], "1472046650": [1], "1472162272": [1], "1472554026": [1], "1473065940": [1], "1473503587": [1], "1473674449": [1], "1473977787": [1], "1474258771": [1], "1474358955": [1], "1474510474": [1], "1474511482": [1], "1474606509": [1], "1475207700": [1], "1475339096": [1], "1475369080": [1], "1475412229": [1], "1475648173": [1], "1475724461": [1], "1475757274": [1], "1475800813": [1], "1475910301": [1], "1476194609": [1], "1476500985": [1], "1476546089": [1], "1476580805": [1], "1476802463": [1], "1476843389": [1], "1476990840": [1], "1477631559": [1], "1478145427": [1], "1478280381": [1], "1478480722": [1], "1478747602": [1], "1478747982": [1], "1478832579": [1], "1478846165": [1], "1478970554": [1], "1479016266": [1], "1479058403": [1], "1479239327": [1], "1479659943": [1], "1479708200": [1], "1479836406": [1], "1479958185": [1], "1480079478": [1], "1480260965": [1], "1480358699": [1], "1480368457": [1], "1480481460": [1], "1481235518": [1], "1481302694": [1], "1481442513": [1], "1481529140": [1], "1481707099": [1], "1481979872": [1], "1482642951": [1], "1482651725": [1], "1482971031": [1], "1484577073": [1], "1484607196": [1], "1484773169": [1], "1485194036": [1], "1485416461": [1], "1485611269": [1], "1485756393": [1], "1486076319": [1], "1486405298": [1], "1486503446": [1], "1486843736": [1], "1486994990": [1], "1487049998": [1], "1487088463": [1], "1487508297": [1], "1487605348": [1], "1488092673": [1], "1488362429": [1], "1488553881": [1], "1488677034": [1], "1488739302": [1], "1488902957": [1], "1489093789": [1], "1489301958": [1], "1489542802": [1], "1489568553": [1], "1489665498": [1], "1489744669": [1], "1489841283": [1], "1489948055": [1], "1489981267": [1], "1490404424": [1], "1490467055": [1], "1490719961": [1], "1490918128": [1], "1490974986": [1], "1491259508": [1], "1491415665": [1], "1491421624": [1], "1491590912": [1], "1491685508": [1], "1491723181": [1], "1492748698": [1], "1492833980": [1], "1493311026": [1], "1494188311": [1], "1494246308": [1], "1494365268": [1], "1494418988": [1], "1494841167": [1], "1495070006": [1], "1495242832": [1], "1495404612": [1], "1495583219": [1], "1495974135": [1], "1496234127": [1], "1496488544": [1], "1496766082": [1], "1496859581": [1], "1496872923": [1], "1497260956": [1], "1497459231": [1], "1497579600": [1], "1497666565": [1], "1497694830": [1], "1498030752": [1], "1498067859": [1], "1498241416": [1], "1498451780": [1], "1498972199": [1], "1499003531": [1], "1499131518": [1], "1499308564": [1], "1499584178": [1], "1499599446": [1], "1499759120": [1], "1499899663": [1], "1500102652": [1], "1500360186": [1], "1500554586": [1], "1500838057": [1], "1501106486": [1], "1501267745": [1], "1501293157": [1], "1502112379": [1], "1502292566": [1], "1502389107": [1], "1502523013": [1], "1502826287": [1], "1503416986": [1], "1503525028": [1], "1504118117": [1], "1504140193": [1], "1504294522": [1], "1504437672": [1], "1505281450": [1], "1505336829": [1], "1505548901": [1], "1505666396": [1], "1505797947": [1], "1505832499": [1], "1506341022": [1], "1506741126": [1], "1506908081": [1], "1507151347": [1], "1507179552": [1], "1507716851": [1], "1508155598": [1], "1508212870": [1], "1508477103": [1], "1508896972": [1], "1509377484": [1], "1509528821": [1], "1510331182": [1], "1510907901": [1], "1511026544": [1], "1511595574": [1], "1512142276": [1], "1512390118": [1], "1512410608": [1], "1512414131": [1], "1512675703": [1], "1513497773": [1], "1513552073": [1], "1513592066": [1], "1513837778": [1], "1513900520": [1], "1514072399": [1], "1514622755": [1], "1514732292": [1], "1515076144": [1], "1515134255": [1], "1515486157": [1], "1515745050": [1], "1515787172": [1], "1515854845": [1], "1516042808": [1], "1516074606": [1], "1516218925": [1], "1516496294": [1], "1516708919": [1], "1517103939": [1], "1517263212": [1], "1517333887": [1], "1517430596": [1], "1517937501": [1], "1517975280": [1], "1518358174": [1], "1518460164": [1], "1518725876": [1], "1519020457": [1], "1519225687": [1], "1519248141": [1], "1519560178": [1], "1519618427": [1], "1520014218": [1], "1520060786": [1], "1520089805": [1], "1520093760": [1], "1520531335": [1], "1520816175": [1], "1520861591": [1], "1520873662": [1], "1520936441": [1], "1521227015": [1], "1521285533": [1], "1521500704": [1], "1521671566": [1], "1521707042": [1], "1521973417": [1], "1522002118": [1], "1522023484": [1], "1522055027": [1], "1522134992": [1], "1522535438": [1], "1522580556": [1], "1522591237": [1], "1522678811": [1], "1522707059": [1], "1522719143": [1], "1522778327": [1], "1522974874": [1], "1524022789": [1], "1524047401": [1], "1524074048": [1], "1524464264": [1], "1524733282": [1], "1524791640": [1], "1524863014": [1], "1525106639": [1], "1525342480": [1], "1525579131": [1], "1525868378": [1], "1525878419": [1], "1526025221": [1], "1526647354": [1], "1526717523": [1], "1526966315": [1], "1527037303": [1], "1527594184": [1], "1527707359": [1], "1528605667": [1], "1528624228": [1], "1528748396": [1], "1528859788": [1], "1529303020": [1], "1529447872": [1], "1529561349": [1], "1529649642": [1], "1529773729": [1], "1529874646": [1], "1529901915": [1], "1530538235": [1], "1530566179": [1], "1530676746": [1], "1531002686": [1], "1531119222": [1], "1531141733": [1], "1531350795": [1], "1531500789": [1], "1531732202": [1], "1531788368": [1], "1531818795": [1], "1532185980": [1], "1532311371": [1], "1532655693": [1], "1532682012": [1], "1532838404": [1], "1532840942": [1], "1532892156": [1], "1533272628": [1], "1534265309": [1], "1534457469": [1], "1535102131": [1], "1535136618": [1], "1535146509": [1], "1535257440": [1], "1535273835": [1], "1535692379": [1], "1536103712": [1], "1536320057": [1], "1536379831": [1], "1536718016": [1], "1536755901": [1], "1537416158": [1], "1537482971": [1], "1537531481": [1], "1537603838": [1], "1538090709": [1], "1538366368": [1], "1538667538": [1], "1538810696": [1], "1538857196": [1], "1539154497": [1], "1539476625": [1], "1539598600": [1], "1539685184": [1], "1540164196": [1], "1540193373": [1], "1540299354": [1], "1540675695": [1], "1540791588": [1], "1540961696": [1], "1541252135": [1], "1541271618": [1], "1541362222": [1], "1541651870": [1], "1541685629": [1], "1541854095": [1], "1542044258": [1], "1542140041": [1], "1542143367": [1], "1542363517": [1], "1543049847": [1], "1543291986": [1], "1543310196": [1], "1543452454": [1], "1543463946": [1], "1543473995": [1], "1543485219": [1], "1543494651": [1], "1544126101": [1], "1544221537": [1], "1545289312": [1], "1546084850": [1], "1546267923": [1], "1546473829": [1], "1546669512": [1], "1546935800": [1], "1547063495": [1], "1547175183": [1], "1547333218": [1], "1547558675": [1], "1547559067": [1], "1548415756": [1], "1549211089": [1], "1549380587": [1], "1549451766": [1], "1549991958": [1], "1550252395": [1], "1550315636": [1], "1550739768": [1], "1550876879": [1], "1550947722": [1], "1551728963": [1], "1551986034": [1], "1552253548": [1], "1552653559": [1], "1552663895": [1], "1552665612": [1], "1552737314": [1], "1552739360": [1], "1553177510": [1], "1553622803": [1], "1553660723": [1], "1553682003": [1], "1554665678": [1], "1554876581": [1], "1555054163": [1], "1555074598": [1], "1555117740": [1], "1555128616": [1], "1555640171": [1], "1556161080": [1], "1556356109": [1], "1556601297": [1], "1556757934": [1], "1556789497": [1], "1556958836": [1], "1557168466": [1], "1558169295": [1], "1558527341": [1], "1558717974": [1], "1558868358": [1], "1559044645": [1], "1559093940": [1], "1559185336": [1], "1559212550": [1], "1559478753": [1], "1559704402": [1], "1559916701": [1], "1560861505": [1], "1561020658": [1], "1561132257": [1], "1561496045": [1], "1561630300": [1], "1561731213": [1], "1561960889": [1], "1562256647": [1], "1562648649": [1], "1562826632": [1], "1563129429": [1], "1563447598": [1], "1563811499": [1], "1564227789": [1], "1564689091": [1], "1564891319": [1], "1564909743": [1], "1564935922": [1], "1564990811": [1], "1565161757": [1], "1565590430": [1], "1566190428": [1], "1566258422": [1], "1566338915": [1], "1566516123": [1], "1566660953": [1], "1566753667": [1], "1566980257": [1], "1567044271": [1], "1567092018": [1], "1567199667": [1], "1567243416": [1], "1567306235": [1], "1567618158": [1], "1567930564": [1], "1568098952": [1], "1568146003": [1], "1568255303": [1], "1568291053": [1], "1568605996": [1], "1568628305": [1], "1568649189": [1], "1568713774": [1], "1569077860": [1], "1569147815": [1], "1569330359": [1], "1569540486": [1], "1570048551": [1], "1570533215": [1], "1570689988": [1], "1570747712": [1], "1570754971": [1], "1570793795": [1], "1570935616": [1], "1570959222": [1], "1571042094": [1], "1571077537": [1], "1571133309": [1], "1571335201": [1], "1571351698": [1], "1571362379": [1], "1571463499": [1], "1571791570": [1], "1572114553": [1], "1572202530": [1], "1572385240": [1], "1572516827": [1], "1572594245": [1], "1573037149": [1], "1573126037": [1], "1573195002": [1], "1573445918": [1], "1573490546": [1], "1573504628": [1], "1573754841": [1], "1574220430": [1], "1574430362": [1], "1574476528": [1], "1575150097": [1], "1575235804": [1], "1575278969": [1], "1575395439": [1], "1575406169": [1], "1575446774": [1], "1575712685": [1], "1575743862": [1], "1576437003": [1], "1576669103": [1], "1577017314": [1], "1577367978": [1], "1577434717": [1], "1577452581": [1], "1577514001": [1], "1577622025": [1], "1577812089": [1], "1578048406": [1], "1578270755": [1], "1578770047": [1], "1578853980": [1], "1578934421": [1], "1578944223": [1], "1579027543": [1], "1579048090": [1], "1579099449": [1], "1579838742": [1], "1580434700": [1], "1580690653": [1], "1580741856": [1], "1580760101": [1], "1580953245": [1], "1581072448": [1], "1581198959": [1], "1581579619": [1], "1581833370": [1], "1582420439": [1], "1582915590": [1], "1583368195": [1], "1583494487": [1], "1583586512": [1], "1584073234": [1], "1584121985": [1], "1585090862": [1], "1585239286": [1], "1585421636": [1], "1585690456": [1], "1585706747": [1], "1585738010": [1], "1585739115": [1], "1585958935": [1], "1586142369": [1], "1586246218": [1], "1587019972": [1], "1587180449": [1], "1587348751": [1], "1587959235": [1], "1588277530": [1], "1588782172": [1], "1588856806": [1], "1588879401": [1], "1589147301": [1], "1589177307": [1], "1589288789": [1], "1589402121": [1], "1589635762": [1], "1589705219": [1], "1590009990": [1], "1590217621": [1], "1590478140": [1], "1590784448": [1], "1591312829": [1], "1591388819": [1], "1591741838": [1], "1591945806": [1], "1592490824": [1], "1592763527": [1], "1593003305": [1], "1593132128": [1], "1593145684": [1], "1593181207": [1], "1593870207": [1], "1594390295": [1], "1594638134": [1], "1594829953": [1], "1595621312": [1], "1595722498": [1], "1596164925": [1], "1596414385": [1], "1596473143": [1], "1596486333": [1], "1596746395": [1], "1596766569": [1], "1596850011": [1], "1597097868": [1], "1597282283": [1], "1597458283": [1], "1597509030": [1], "1597821821": [1], "1598286282": [1], "1598537901": [1], "1598539754": [1], "1598688863": [1], "1598734245": [1], "1598986190": [1], "1599479886": [1], "1599637304": [1], "1599767507": [1], "1600091063": [1], "1600495569": [1], "1600847937": [1], "1600955507": [1], "1600960579": [1], "1601046153": [1], "1601110578": [1], "1601453159": [1], "1601470594": [1], "1602337394": [1], "1602459429": [1], "1602903343": [1], "1603079622": [1], "1603198191": [1], "1603280167": [1], "1603375383": [1], "1603479207": [1], "1603721816": [1], "1603873013": [1], "1604035109": [1], "1604145567": [1], "1604437359": [1], "1604578291": [1], "1604988400": [1], "1605295789": [1], "1605728161": [1], "1605777117": [1], "1605816700": [1], "1605830911": [1], "1606087533": [1], "1606350162": [1], "1606460924": [1], "1606508930": [1], "1607487361": [1], "1607501729": [1], "1607635911": [1], "1607701478": [1], "1607791596": [1], "1608364335": [1], "1608473836": [1], "1608891571": [1], "1608898463": [1], "1609343268": [1], "1609376818": [1], "1609572309": [1], "1610023724": [1], "1610137295": [1], "1610390264": [1], "1610518046": [1], "1611279206": [1], "1612425332": [1], "1612445595": [1], "1612468626": [1], "1612986740": [1], "1613024945": [1], "1613206270": [1], "1613341671": [1], "1613459412": [1], "1613520255": [1], "1614137010": [1], "1614274652": [1], "1615094743": [1], "1615275457": [1], "1615400917": [1], "1615479494": [1], "1615798861": [1], "1616435580": [1], "1616502526": [1], "1616605516": [1], "1616905698": [1], "1617433210": [1], "1617563355": [1], "1617795538": [1], "1617868880": [1], "1617955629": [1], "1618225673": [1], "1618371338": [1], "1618435813": [1], "1618470212": [1], "1618519400": [1], "1618839509": [1], "1619038482": [1], "1619289973": [1], "1619298835": [1], "1619469188": [1], "1619554198": [1], "1619635529": [1], "1619835015": [1], "1619891004": [1], "1619951466": [1], "1620244833": [1], "1620769782": [1], "1620791683": [1], "1621156120": [1], "1621189768": [1], "1621297648": [1], "1621682963": [1], "1621881570": [1], "1622211139": [1], "1622268606": [1], "1622468241": [1], "1623176802": [1], "1623722613": [1], "1623869326": [1], "1624101242": [1], "1624347800": [1], "1624408760": [1], "1624839668": [1], "1625103081": [1], "1625366915": [1], "1625437998": [1], "1625459070": [1], "1625538044": [1], "1625585866": [1], "1625832243": [1], "1626080326": [1], "1626326515": [1], "1626401341": [1], "1626636809": [1], "1626682653": [1], "1626973008": [1], "1627252840": [1], "1627578291": [1], "1627785325": [1], "1628172422": [1], "1628486746": [1], "1629431134": [1], "1629583594": [1], "1629929303": [1], "1630002454": [1], "1630075013": [1], "1630272838": [1], "1630640596": [1], "1630647318": [1], "1630886824": [1], "1631019388": [1], "1631480868": [1], "1631723546": [1], "1631890180": [1], "1632104383": [1], "1632283591": [1], "1632950492": [1], "1633018820": [1], "1633283179": [1], "1633336941": [1], "1633382022": [1], "1633445392": [1], "1633508749": [1], "1633791817": [1], "1633926816": [1], "1633965935": [1], "1634228136": [1], "1634436003": [1], "1635238543": [1], "1635654478": [1], "1635933732": [1], "1635960762": [1], "1636018418": [1], "1636019764": [1], "1636112471": [1], "1636964096": [1], "1637059904": [1], "1637384237": [1], "1638825189": [1], "1639079545": [1], "1639211494": [1], "1639242586": [1], "1639308381": [1], "1639698074": [1], "1639733465": [1], "1640163401": [1], "1640964874": [1], "1641325477": [1], "1641561213": [1], "1641666549": [1], "1641898347": [1], "1642222554": [1], "1642368873": [1], "1642621781": [1], "1642650026": [1], "1642671533": [1], "1642811788": [1], "1643389568": [1], "1643399034": [1], "1643627698": [1], "1643802683": [1], "1643905596": [1], "1644371392": [1], "1644394873": [1], "1644396729": [1], "1644848007": [1], "1645324278": [1], "1645515905": [1], "1645636572": [1], "1645653795": [1], "1645795092": [1], "1646154114": [1], "1646868595": [1], "1646950643": [1], "1647341651": [1], "1647667779": [1], "1647674996": [1], "1647681334": [1], "1648201590": [1], "1648410970": [1], "1648590564": [1], "1648898593": [1], "1649324601": [1], "1649448187": [1], "1649659509": [1], "1649661421": [1], "1649775592": [1], "1650042231": [1], "1650255749": [1], "1650317810": [1], "1650988081": [1], "1651068165": [1], "1651354298": [1], "1651662775": [1], "1651679349": [1], "1652186966": [1], "1652450920": [1], "1652799798": [1], "1652997920": [1], "1653730908": [1], "1654129021": [1], "1654262321": [1], "1654283163": [1], "1654556837": [1], "1654637943": [1], "1655341269": [1], "1655370770": [1], "1655386158": [1], "1655514329": [1], "1656232256": [1], "1656257393": [1], "1657041010": [1], "1657357741": [1], "1657563756": [1], "1657645713": [1], "1658359359": [1], "1658401049": [1], "1658895749": [1], "1659391765": [1], "1659522333": [1], "1659917804": [1], "1660207623": [1], "1660644258": [1], "1660696096": [1], "1660899652": [1], "1661712043": [1], "1662042392": [1], "1662357488": [1], "1662373166": [1], "1662715158": [1], "1662940931": [1], "1663813178": [1], "1663831361": [1], "1664016028": [1], "1664426468": [1], "1664805756": [1], "1664888979": [1], "1665081184": [1], "1665086631": [1], "1665131861": [1], "1665225350": [1], "1665474871": [1], "1665523624": [1], "1665673273": [1], "1665703901": [1], "1665923334": [1], "1665980316": [1], "1666164926": [1], "1666454011": [1], "1666710634": [1], "1666752701": [1], "1666800156": [1], "1666954904": [1], "1667081019": [1], "1667139608": [1], "1667176814": [1], "1667314642": [1], "1667716444": [1], "1667885289": [1], "1667951268": [1], "1668627533": [1], "1669092777": [1], "1669607579": [1], "1669714139": [1], "1669804960": [1], "1670133545": [1], "1670545023": [1], "1670579959": [1], "1670666810": [1], "1670796259": [1], "1671503658": [1], "1671952454": [1], "1672421613": [1], "1672900610": [1], "1673276827": [1], "1673870148": [1], "1673938644": [1], "1674690745": [1], "1674980379": [1], "1675048306": [1], "1675065191": [1], "1675310348": [1], "1675398837": [1], "1675514630": [1], "1675749549": [1], "1675990121": [1], "1676112416": [1], "1676338213": [1], "1676512463": [1], "1676594773": [1], "1676924661": [1], "1676998850": [1], "1677114091": [1], "1677929229": [1], "1679163136": [1], "1679223796": [1], "1679403718": [1], "1679427449": [1], "1679772797": [1], "1680191276": [1], "1680298417": [1], "1680524143": [1], "1680552522": [1], "1680737327": [1], "1680967825": [1], "1681046112": [1], "1681339549": [1], "1681868178": [1], "1682076482": [1], "1682304802": [1], "1682498651": [1], "1682698497": [1], "1682808687": [1], "1683000252": [1], "1683450537": [1], "1683495948": [1], "1683548438": [1], "1683564519": [1], "1683944947": [1], "1684046644": [1], "1684181189": [1], "1684524808": [1], "1684749678": [1], "1684918491": [1], "1685067331": [1], "1685216155": [1], "1685558211": [1], "1685661895": [1], "1686554163": [1], "1686593024": [1], "1686671286": [1], "1687180049": [1], "1687336974": [1], "1687528170": [1], "1687866536": [1], "1688039250": [1], "1688138358": [1], "1688206016": [1], "1688610947": [1], "1688703594": [1], "1689294448": [1], "1689499867": [1], "1689799253": [1], "1690196414": [1], "1690419229": [1], "1690423558": [1], "1690536429": [1], "1690589299": [1], "1690745754": [1], "1691105202": [1], "1691552719": [1], "1691617929": [1], "1692602100": [1], "1692743544": [1], "1692784496": [1], "1693060305": [1], "1693110284": [1], "1693130372": [1], "1693614130": [1], "1693956247": [1], "1694193842": [1], "1694885616": [1], "1695035310": [1], "1695115601": [1], "1695322548": [1], "1695509086": [1], "1695898616": [1], "1696312985": [1], "1696584181": [1], "1697526543": [1], "1697582539": [1], "1698818238": [1], "1698956089": [1], "1699123808": [1], "1699142826": [1], "1699253786": [1], "1699407079": [1], "1699540696": [1], "1699634265": [1], "1699798758": [1], "1699863110": [1], "1700044427": [1], "1700494274": [1], "1700708675": [1], "1700759296": [1], "1700958567": [1], "1701055578": [1], "1701074222": [1], "1701168204": [1], "1701600974": [1], "1701813046": [1], "1701889610": [1], "1701890846": [1], "1701920188": [1], "1702285088": [1], "1702326672": [1], "1702704250": [1], "1703118044": [1], "1703277260": [1], "1703586559": [1], "1703745231": [1], "1703795042": [1], "1703866901": [1], "1703928251": [1], "1704080352": [1], "1704294000": [1], "1704371544": [1], "1704418998": [1], "1705244738": [1], "1705585209": [1], "1706292847": [1], "1707147152": [1], "1707439202": [1], "1707446400": [1], "1707565637": [1], "1707589719": [1], "1707881974": [1], "1707929877": [1], "1708132269": [1], "1708252017": [1], "1709026874": [1], "1709939708": [1], "1709962890": [1], "1709973352": [1], "1710469473": [1], "1710897237": [1], "1711042511": [1], "1711184111": [1], "1711429319": [1], "1711449525": [1], "1711654208": [1], "1711694183": [1], "1711777715": [1], "1711881765": [1], "1712564853": [1], "1712597938": [1], "1713039515": [1], "1713171248": [1], "1713413018": [1], "1713718315": [1], "1713795125": [1], "1714315241": [1], "1714406549": [1], "1714956833": [1], "1715191179": [1], "1715208473": [1], "1715292896": [1], "1716327510": [1], "1716357028": [1], "1716621706": [1], "1717510158": [1], "1717525490": [1], "1717660429": [1], "1718063031": [1], "1718349968": [1], "1718396143": [1], "1718417669": [1], "1718434478": [1], "1718692025": [1], "1718773907": [1], "1719328017": [1], "1719703626": [1], "1719720258": [1], "1719783953": [1], "1719806158": [1], "1719901387": [1], "1720325044": [1], "1720479598": [1], "1720493809": [1], "1720861619": [1], "1720881495": [1], "1721437825": [1], "1722385892": [1], "1722677007": [1], "1722938021": [1], "1722969834": [1], "1723148725": [1], "1723219445": [1], "1724011407": [1], "1724188675": [1], "1724557524": [1], "1725080824": [1], "1725324762": [1], "1725497123": [1], "1725687283": [1], "1725798296": [1], "1726894946": [1], "1727513181": [1], "1727552519": [1], "1727784820": [1], "1727870086": [1], "1727894982": [1], "1727914702": [1], "1727961838": [1], "1728152243": [1], "1728171512": [1], "1728483411": [1], "1728617473": [1], "1728697101": [1], "1728721594": [1], "1728929641": [1], "1729039265": [1], "1729585997": [1], "1729598341": [1], "1729693187": [1], "1729911117": [1], "1729923423": [1], "1730166599": [1], "1730677568": [1], "1730795995": [1], "1730998547": [1], "1731105544": [1], "1731231547": [1], "1731274472": [1], "1731717247": [1], "1731806752": [1], "1731977134": [1], "1732183316": [1], "1732320127": [1], "1733719052": [1], "1734127536": [1], "1734312674": [1], "1734539256": [1], "1734556151": [1], "1734655489": [1], "1735195728": [1], "1735225608": [1], "1735480902": [1], "1735614305": [1], "1735827453": [1], "1736028617": [1], "1736067242": [1], "1736230718": [1], "1736720004": [1], "1736767875": [1], "1736989411": [1], "1737589429": [1], "1737634672": [1], "1737682037": [1], "1737775965": [1], "1737863499": [1], "1738114092": [1], "1738981138": [1], "1739263812": [1], "1739401508": [1], "1739544503": [1], "1739545954": [1], "1739940812": [1], "1740188142": [1], "1740960238": [1], "1741119525": [1], "1741147246": [1], "1741666403": [1], "1742929486": [1], "1743748161": [1], "1744599485": [1], "1744615367": [1], "1745011148": [1], "1745077506": [1], "1745572887": [1], "1746082614": [1], "1746130176": [1], "1746201256": [1], "1746205469": [1], "1746409138": [1], "1746428129": [1], "1746579183": [1], "1746596795": [1], "1746683106": [1], "1746774175": [1], "1746834454": [1], "1746944210": [1], "1747085206": [1], "1747100490": [1], "1747295366": [1], "1747389423": [1], "1747492131": [1], "1747719450": [1], "1747826419": [1], "1748187758": [1], "1748199673": [1], "1749144260": [1], "1749224479": [1], "1749377700": [1], "1749915072": [1], "1750222489": [1], "1750244741": [1], "1750499351": [1], "1750557669": [1], "1750938638": [1], "1751000319": [1], "1751310138": [1], "1751574631": [1], "1751885268": [1], "1752115328": [1], "1752134302": [1], "1752284736": [1], "1752294393": [1], "1752300589": [1], "1752578917": [1], "1752626584": [1], "1752797615": [1], "1752993732": [1], "1753342906": [1], "1754002051": [1], "1754088693": [1], "1754286353": [1], "1754882324": [1], "1755041704": [1], "1755591010": [1], "1756116037": [1], "1756467769": [1], "1756800620": [1], "1756833364": [1], "1756963565": [1], "1757533406": [1], "1757710072": [1], "1757724856": [1], "1758072870": [1], "1758136404": [1], "1758680067": [1], "1758974255": [1], "1759091917": [1], "1759614358": [1], "1759786710": [1], "1760464917": [1], "1760842087": [1], "1761340843": [1], "1761660211": [1], "1761838144": [1], "1762328069": [1], "1762349453": [1], "1762591793": [1], "1762620041": [1], "1762654786": [1], "1762750495": [1], "1763280923": [1], "1764056434": [1], "1764320052": [1], "1764555001": [1], "1765504075": [1], "1765512835": [1], "1765736029": [1], "1765783341": [1], "1766332727": [1], "1766603168": [1], "1767332029": [1], "1767361588": [1], "1767537129": [1], "1768784102": [1], "1769090946": [1], "1769154645": [1], "1769262383": [1], "1769368055": [1], "1769413705": [1], "1770110155": [1], "1770145795": [1], "1770405384": [1], "1770612223": [1], "1770738395": [1], "1771219129": [1], "1771621479": [1], "1772015265": [1], "1772140989": [1], "1772437770": [1], "1772457286": [1], "1773104602": [1], "1773183680": [1], "1773210490": [1], "1773253769": [1], "1773885577": [1], "1774163954": [1], "1774583695": [1], "1775100640": [1], "1775130405": [1], "1775183378": [1], "1775377064": [1], "1775950747": [1], "1776237673": [1], "1776267586": [1], "1777746923": [1], "1777779645": [1], "1777857059": [1], "1778024899": [1], "1778278491": [1], "1778317926": [1], "1778830383": [1], "1779204258": [1], "1779208293": [1], "1779276322": [1], "1779571384": [1], "1780034332": [1], "1780077444": [1], "1780822792": [1], "1781353265": [1], "1781426102": [1], "1782349059": [1], "1783031438": [1], "1783257007": [1], "1783701561": [1], "1783845424": [1], "1783964613": [1], "1784088237": [1], "1784214796": [1], "1784279212": [1], "1784300608": [1], "1785018295": [1], "1785148118": [1], "1786257247": [1], "1786269597": [1], "1786330296": [1], "1786355557": [1], "1786719193": [1], "1787175603": [1], "1787348140": [1], "1787694076": [1], "1788248188": [1], "1788463904": [1], "1788572663": [1], "1788733938": [1], "1788784719": [1], "1789259821": [1], "1789555134": [1], "1789621467": [1], "1789649007": [1], "1789734406": [1], "1789785348": [1], "1789833164": [1], "1789866515": [1], "1790293558": [1], "1790343376": [1], "1790831033": [1], "1791508853": [1], "1791574719": [1], "1791944003": [1], "1792414740": [1], "1792429828": [1], "1792581290": [1], "1793108564": [1], "1793475331": [1], "1794060171": [1], "1794062720": [1], "1794102697": [1], "1794780464": [1], "1794984677": [1], "1794995939": [1], "1795587621": [1], "1795857498": [1], "1796055930": [1], "1796598897": [1], "1796833485": [1], "1797046115": [1], "1797076632": [1], "1797393004": [1], "1797397707": [1], "1797702679": [1], "1797880886": [1], "1798441940": [1], "1798470799": [1], "1798690096": [1], "1798700363": [1], "1799027638": [1], "1799093025": [1], "1799208978": [1], "1799346476": [1], "1799512650": [1], "1800218467": [1], "1800253610": [1], "1800421430": [1], "1801065331": [1], "1801322351": [1], "1801950655": [1], "1802227058": [1], "1802548800": [1], "1802590857": [1], "1802963290": [1], "1803000562": [1], "1803131321": [1], "1803653582": [1], "1803912483": [1], "1804047805": [1], "1804468011": [1], "1805153307": [1], "1805416657": [1], "1805655275": [1], "1805810785": [1], "1806465825": [1], "1806635988": [1], "1806763717": [1], "1806820969": [1], "1806853207": [1], "1806998731": [1], "1807011719": [1], "1807096800": [1], "1807400452": [1], "1807963711": [1], "1807997808": [1], "1808164755": [1], "1808485058": [1], "1808874449": [1], "1809620847": [1], "1810214365": [1], "1810365301": [1], "1810682551": [1], "1810871800": [1], "1810930596": [1], "1811061080": [1], "1811152049": [1], "1811187340": [1], "1811837525": [1], "1812069955": [1], "1812252503": [1], "1812391317": [1], "1813351003": [1], "1813905101": [1], "1814395315": [1], "1814551029": [1], "1815013163": [1], "1815324122": [1], "1815917497": [1], "1816031518": [1], "1816364290": [1], "1816708213": [1], "1817222953": [1], "1817491204": [1], "1818632307": [1], "1818842869": [1], "1820137851": [1], "1820245750": [1], "1820347455": [1], "1820567527": [1], "1820818260": [1], "1820910256": [1], "1821124660": [1], "1821128242": [1], "1821131065": [1], "1821253147": [1], "1821733591": [1], "1823237228": [1], "1823775210": [1], "1823977603": [1], "1824423379": [1], "1824564054": [1], "1824638793": [1], "1824651983": [1], "1824751588": [1], "1824936625": [1], "1825909008": [1], "1825923736": [1], "1825979399": [1], "1826688479": [1], "1826732473": [1], "1826848693": [1], "1827144316": [1], "1827328361": [1], "1827507895": [1], "1827578296": [1], "1828500723": [1], "1828662219": [1], "1828941602": [1], "1829028270": [1], "1829070205": [1], "1829463124": [1], "1829955470": [1], "1830473571": [1], "1830476969": [1], "1830551449": [1], "1830680887": [1], "1831103429": [1], "1831110037": [1], "1831185282": [1], "1831201168": [1], "1832000180": [1], "1832023876": [1], "1832135709": [1], "1832362894": [1], "1832734594": [1], "1832787538": [1], "1832847644": [1], "1833403645": [1], "1833452674": [1], "1833863468": [1], "1834005432": [1], "1834117257": [1], "1834255990": [1], "1834498755": [1], "1834677864": [1], "1835699831": [1], "1835715295": [1], "1835910261": [1], "1836032517": [1], "1836131723": [1], "1836263670": [1], "1836343293": [1], "1836674677": [1], "1836913025": [1], "1837229322": [1], "1837742668": [1], "1837904828": [1], "1838574438": [1], "1838956721": [1], "1839512080": [1], "1839782023": [1], "1839839646": [1], "1839869631": [1], "1839942448": [1], "1840168632": [1], "1840330081": [1], "1840379505": [1], "1840515745": [1], "1840517015": [1], "1840583506": [1], "1840865451": [1], "1841010794": [1], "1841037143": [1], "1841070648": [1], "1841382538": [1], "1841501594": [1], "1842110809": [1], "1842248263": [1], "1842417395": [1], "1842695219": [1], "1842900965": [1], "1842922917": [1], "1843088857": [1], "1843093657": [1], "1843207488": [1], "1843247714": [1], "1843448504": [1], "1843541775": [1], "1843545813": [1], "1843630627": [1], "1843655028": [1], "1844241264": [1], "1844281251": [1], "1844313732": [1], "1844339179": [1], "1844384968": [1], "1844694350": [1], "1844747193": [1], "1844797374": [1], "1844945850": [1], "1845543647": [1], "1845546602": [1], "1846211883": [1], "1846219596": [1], "1846439807": [1], "1846533801": [1], "1846617080": [1], "1846969946": [1], "1847606925": [1], "1847754808": [1], "1847961548": [1], "1848024236": [1], "1848177019": [1], "1848667276": [1], "1848851066": [1], "1848888197": [1], "1848955732": [1], "1849171563": [1], "1849237577": [1], "1849475381": [1], "1849565818": [1], "1849695115": [1], "1849752441": [1], "1850774713": [1], "1850813852": [1], "1850943696": [1], "1851005760": [1], "1851899851": [1], "1852124662": [1], "1852285127": [1], "1852507471": [1], "1852686106": [1], "1852906882": [1], "1853234459": [1], "1853386756": [1], "1853438806": [1], "1853548864": [1], "1853612766": [1], "1854518558": [1], "1855192468": [1], "1855230110": [1], "1855370808": [1], "1855438000": [1], "1855497368": [1], "1855677163": [1], "1855984314": [1], "1856120916": [1], "1856687899": [1], "1857221231": [1], "1857504723": [1], "1858231000": [1], "1858251674": [1], "1858307449": [1], "1858354204": [1], "1858421927": [1], "1858806953": [1], "1858946668": [1], "1859168203": [1], "1859302783": [1], "1860005326": [1], "1860114256": [1], "1860219762": [1], "1860299149": [1], "1860463889": [1], "1860659987": [1], "1860833993": [1], "1860857172": [1], "1861074418": [1], "1861518533": [1], "1861842574": [1], "1862041357": [1], "1862043375": [1], "1862137221": [1], "1862145918": [1], "1862267708": [1], "1862626272": [1], "1862669775": [1], "1862834905": [1], "1862923089": [1], "1863697572": [1], "1863827748": [1], "1864444130": [1], "1864595893": [1], "1864649418": [1], "1865001175": [1], "1865288549": [1], "1865472442": [1], "1865617839": [1], "1865656768": [1], "1865741038": [1], "1865993724": [1], "1865995984": [1], "1866282226": [1], "1866343252": [1], "1866418844": [1], "1866571708": [1], "1866685668": [1], "1866825867": [1], "1867151395": [1], "1867240653": [1], "1867382313": [1], "1867511828": [1], "1867707710": [1], "1867771991": [1], "1868228984": [1], "1868839403": [1], "1869308495": [1], "1869360110": [1], "1869546037": [1], "1869550926": [1], "1870169280": [1], "1870506675": [1], "1870536237": [1], "1870819488": [1], "1871172238": [1], "1871245463": [1], "1871318646": [1], "1871374219": [1], "1871524392": [1], "1871711547": [1], "1871979922": [1], "1872150252": [1], "1872249545": [1], "1872823178": [1], "1872880928": [1], "1873092782": [1], "1873169715": [1], "1873478126": [1], "1874225087": [1], "1875052895": [1], "1875322697": [1], "1875370799": [1], "1875449450": [1], "1875496815": [1], "1875703765": [1], "1875770690": [1], "1875933490": [1], "1876314407": [1], "1876439036": [1], "1876615183": [1], "1876741368": [1], "1877057824": [1], "1877137730": [1], "1877755295": [1], "1878258828": [1], "1878315049": [1], "1878598249": [1], "1878641771": [1], "1878714766": [1], "1878793939": [1], "1878893420": [1], "1879197774": [1], "1879959759": [1], "1879960070": [1], "1879978132": [1], "1880202337": [1], "1880963465": [1], "1881152561": [1], "1881177118": [1], "1881288070": [1], "1881672815": [1], "1882170670": [1], "1882545585": [1], "1883019630": [1], "1883309183": [1], "1883378416": [1], "1883880120": [1], "1883966465": [1], "1884953792": [1], "1885086930": [1], "1885104388": [1], "1885526996": [1], "1885689552": [1], "1885802266": [1], "1886100862": [1], "1886353852": [1], "1886439343": [1], "1887041537": [1], "1887376484": [1], "1888172012": [1], "1888442171": [1], "1888557552": [1], "1888828716": [1], "1888842726": [1], "1888969761": [1], "1889243674": [1], "1889319516": [1], "1889358731": [1], "1890256785": [1], "1890644289": [1], "1891076117": [1], "1891134761": [1], "1891473039": [1], "1891601665": [1], "1891611238": [1], "1891693677": [1], "1891832950": [1], "1891914620": [1], "1891973174": [1], "1892123075": [1], "1892443473": [1], "1892522681": [1], "1892523781": [1], "1892590738": [1], "1892794187": [1], "1892847876": [1], "1893118800": [1], "1893213074": [1], "1893306501": [1], "1893313210": [1], "1893665469": [1], "1893746109": [1], "1893850500": [1], "1894197870": [1], "1894211546": [1], "1894296635": [1], "1894481502": [1], "1894667844": [1], "1894678918": [1], "1895343741": [1], "1895809176": [1], "1895919607": [1], "1895945237": [1], "1895962559": [1], "1896393752": [1], "1896886842": [1], "1897200323": [1], "1898060799": [1], "1898199383": [1], "1898794071": [1], "1899147319": [1], "1899531767": [1], "1900286022": [1], "1900611773": [1], "1900693320": [1], "1901140508": [1], "1901862048": [1], "1902415893": [1], "1902578338": [1], "1902879551": [1], "1902947618": [1], "1903284468": [1], "1903492967": [1], "1903668317": [1], "1903755489": [1], "1904216530": [1], "1904620682": [1], "1904623097": [1], "1904634617": [1], "1905029169": [1], "1905228883": [1], "1905415424": [1], "1905785420": [1], "1905897567": [1], "1906094024": [1], "1906135629": [1], "1906686782": [1], "1907187930": [1], "1907215601": [1], "1907368739": [1], "1908171086": [1], "1908891721": [1], "1909475520": [1], "1909524124": [1], "1909583067": [1], "1909731107": [1], "1909757883": [1], "1909852405": [1], "1909920638": [1], "1909925584": [1], "1910227469": [1], "1910712018": [1], "1910884965": [1], "1911561553": [1], "1911694337": [1], "1912059660": [1], "1912145550": [1], "1912321099": [1], "1913331444": [1], "1913332607": [1], "1913436922": [1], "1913976819": [1], "1914179756": [1], "1915035443": [1], "1915271621": [1], "1915742101": [1], "1917020144": [1], "1917285760": [1], "1917459890": [1], "1917862585": [1], "1917867597": [1], "1917918617": [1], "1918291028": [1], "1918481974": [1], "1918638909": [1], "1918796226": [1], "1918797354": [1], "1919400828": [1], "1919583896": [1], "1919721965": [1], "1919801347": [1], "1919875912": [1], "1920127434": [1], "1920360515": [1], "1920654508": [1], "1920829209": [1], "1921211474": [1], "1921647468": [1], "1922025122": [1], "1922302786": [1], "1922351441": [1], "1922474750": [1], "1922775549": [1], "1922812394": [1], "1922825375": [1], "1922962990": [1], "1923515162": [1], "1923641365": [1], "1925280364": [1], "1925290847": [1], "1925466289": [1], "1925740036": [1], "1926010573": [1], "1926011466": [1], "1926477203": [1], "1926737385": [1], "1926853035": [1], "1926963571": [1], "1927427634": [1], "1928018682": [1], "1928297486": [1], "1928639716": [1], "1928824369": [1], "1928930330": [1], "1929406693": [1], "1929430977": [1], "1929544513": [1], "1929735752": [1], "1929948980": [1], "1930616508": [1], "1931279505": [1], "1931463010": [1], "1931721934": [1], "1931743365": [1], "1931869057": [1], "1931952357": [1], "1932097054": [1], "1932446127": [1], "1932499913": [1], "1932724121": [1], "1932925652": [1], "1933808210": [1], "1934175365": [1], "1934458519": [1], "1934760967": [1], "1934973067": [1], "1935346847": [1], "1935406736": [1], "1935553715": [1], "1936466762": [1], "1936473986": [1], "1936816562": [1], "1936863426": [1], "1936883737": [1], "1937186982": [1], "1937194846": [1], "1937213584": [1], "1937242955": [1], "1937462317": [1], "1937601562": [1], "1937777162": [1], "1937785642": [1], "1938040576": [1], "1938146172": [1], "1938201291": [1], "1938240048": [1], "1938269495": [1], "1938655905": [1], "1939362382": [1], "1939663083": [1], "1939864463": [1], "1940409250": [1], "1940607042": [1], "1940752591": [1], "1940936341": [1], "1941437793": [1], "1941788960": [1], "1942156305": [1], "1942630382": [1], "1943481560": [1], "1943642415": [1], "1944085352": [1], "1944292160": [1], "1944551434": [1], "1944728841": [1], "1945796012": [1], "1946163695": [1], "1946277998": [1], "1946493511": [1], "1946738484": [1], "1947354592": [1], "1947433322": [1], "1948096821": [1], "1948108443": [1], "1948706540": [1], "1949324484": [1], "1949423561": [1], "1949932148": [1], "1950638997": [1], "1950810675": [1], "1951190890": [1], "1951272770": [1], "1951562760": [1], "1952386209": [1], "1952634826": [1], "1952679332": [1], "1952886682": [1], "1952894534": [1], "1952985399": [1], "1953196923": [1], "1953381862": [1], "1953404617": [1], "1953623675": [1], "1953813146": [1], "1954139942": [1], "1954273974": [1], "1954428990": [1], "1954658827": [1], "1954720401": [1], "1955586612": [1], "1955807237": [1], "1955909493": [1], "1956518514": [1], "1956705209": [1], "1957167353": [1], "1957183692": [1], "1957191975": [1], "1957277555": [1], "1957556023": [1], "1958256647": [1], "1958396542": [1], "1958473272": [1], "1958959575": [1], "1958977633": [1], "1959329669": [1], "1959737499": [1], "1959844846": [1], "1960428398": [1], "1960681987": [1], "1961037093": [1], "1961290059": [1], "1961359889": [1], "1961451215": [1], "1961731240": [1], "1961961808": [1], "1962289936": [1], "1962392750": [1], "1962775794": [1], "1963361524": [1], "1963762415": [1], "1964304238": [1], "1964311195": [1], "1964350775": [1], "1964916516": [1], "1965480379": [1], "1965501609": [1], "1965848141": [1], "1966315377": [1], "1966359946": [1], "1966704630": [1], "1966798447": [1], "1966986689": [1], "1967160163": [1], "1967245579": [1], "1967343997": [1], "1967489476": [1], "1967647944": [1], "1967993566": [1], "1968011981": [1], "1968216256": [1], "1968767891": [1], "1968948816": [1], "1968952120": [1], "1968996517": [1], "1969069289": [1], "1969321986": [1], "1969347393": [1], "1969618075": [1], "1969713665": [1], "1969814538": [1], "1970027158": [1], "1970058034": [1], "1970458937": [1], "1970817528": [1], "1970851930": [1], "1971358366": [1], "1971364134": [1], "1971378720": [1], "1971446404": [1], "1971827805": [1], "1972009646": [1], "1972072761": [1], "1972211923": [1], "1972391637": [1], "1972590708": [1], "1972642437": [1], "1972656271": [1], "1972797485": [1], "1973049232": [1], "1973512095": [1], "1973706571": [1], "1973762765": [1], "1974010169": [1], "1974077857": [1], "1974651059": [1], "1974714665": [1], "1974869048": [1], "1975242476": [1], "1975377179": [1], "1975459622": [1], "1975644275": [1], "1975789715": [1], "1975829497": [1], "1975843954": [1], "1976397755": [1], "1976909549": [1], "1977095138": [1], "1977162411": [1], "1977306010": [1], "1977375096": [1], "1977450236": [1], "1977679224": [1], "1977882545": [1], "1978183916": [1], "1978233078": [1], "1978423311": [1], "1978527579": [1], "1978936756": [1], "1978976337": [1], "1979139687": [1], "1979602018": [1], "1979640142": [1], "1980194991": [1], "1980374221": [1], "1980776124": [1], "1980969336": [1], "1980981939": [1], "1981008706": [1], "1981011029": [1], "1981223408": [1], "1981255797": [1], "1981442185": [1], "1981864146": [1], "1982461630": [1], "1983084428": [1], "1983095172": [1], "1983842332": [1], "1984128916": [1], "1984410983": [1], "1984822558": [1], "1985256322": [1], "1985933927": [1], "1985998899": [1], "1986654607": [1], "1986685285": [1], "1986738780": [1], "1986809101": [1], "1986857272": [1], "1987248800": [1], "1987926884": [1], "1987975486": [1], "1988124741": [1], "1988305505": [1], "1988687969": [1], "1989114553": [1], "1989371608": [1], "1989444438": [1], "1989626993": [1], "1989677569": [1], "1989783209": [1], "1990102109": [1], "1990486009": [1], "1990486620": [1], "1990672484": [1], "1991067186": [1], "1991465212": [1], "1991702841": [1], "1991869542": [1], "1991893238": [1], "1992106942": [1], "1992396206": [1], "1992584243": [1], "1992767929": [1], "1993141613": [1], "1993152456": [1], "1993322246": [1], "1993437187": [1], "1993903323": [1], "1993990431": [1], "1994132577": [1], "1994425820": [1], "1994842388": [1], "1994984465": [1], "1995152187": [1], "1995450754": [1], "1995500945": [1], "1995795432": [1], "1995845622": [1], "1996038155": [1], "1996123742": [1], "1996278098": [1], "1996679291": [1], "1996721096": [1], "1996907753": [1], "1997146363": [1], "1997793565": [1], "1997808689": [1], "1997850672": [1], "1998293805": [1], "1998497718": [1], "1998632552": [1], "1998771304": [1], "1998812545": [1], "1999011992": [1], "1999300160": [1], "1999603665": [1], "1999760479": [1], "1999806303": [1], "1999824246": [1], "1999958138": [1], "2000041153": [1], "2000174958": [1], "2000352033": [1], "2000377829": [1], "2001160447": [1], "2001231139": [1], "2001369130": [1], "2001468129": [1], "2001487746": [1], "2001937266": [1], "2001974146": [1], "2002321410": [1], "2002447057": [1], "2002475287": [1], "2002918254": [1], "2003600151": [1], "2003645717": [1], "2003706407": [1], "2004036972": [1], "2005023973": [1], "2005072584": [1], "2005199380": [1], "2005355499": [1], "2006912118": [1], "2006915087": [1], "2006931686": [1], "2007029243": [1], "2007115111": [1], "2007275179": [1], "2007294549": [1], "2007736849": [1], "2008512983": [1], "2008581869": [1], "2008756500": [1], "2009525397": [1], "2009686645": [1], "2009714229": [1], "2010360529": [1], "2010392512": [1], "2010641171": [1], "2010857451": [1], "2011464736": [1], "2011638302": [1], "2011886971": [1], "2011910081": [1], "2012044969": [1], "2012063290": [1], "2012391721": [1], "2012394180": [1], "2012425057": [1], "2012501604": [1], "2012891300": [1], "2013240385": [1], "2013309557": [1], "2013940632": [1], "2014481952": [1], "2014723133": [1], "2014943931": [1], "2015642505": [1], "2015734346": [1], "2015749590": [1], "2016237729": [1], "2016601497": [1], "2016921180": [1], "2017352288": [1], "2017359027": [1], "2017479430": [1], "2017628569": [1], "2017720328": [1], "2018372495": [1], "2018993768": [1], "2019116010": [1], "2019351759": [1], "2020068129": [1], "2020170201": [1], "2020518699": [1], "2021069191": [1], "2021370933": [1], "2021860533": [1], "2021893696": [1], "2022008378": [1], "2022131570": [1], "2022266645": [1], "2022330928": [1], "2022561620": [1], "2022812484": [1], "2022914789": [1], "2023091160": [1], "2023092406": [1], "2023728829": [1], "2023839301": [1], "2024088148": [1], "2024116575": [1], "2025083538": [1], "2025286758": [1], "2025312867": [1], "2025694591": [1], "2025817863": [1], "2025897681": [1], "2026165662": [1], "2026419546": [1], "2026503351": [1], "2026964547": [1], "2027416794": [1], "2027626935": [1], "2027839933": [1], "2027938920": [1], "2028627353": [1], "2028676505": [1], "2028760130": [1], "2028940002": [1], "2029058639": [1], "2029101658": [1], "2029902512": [1], "2029907937": [1], "2029964863": [1], "2029990774": [1], "2030203259": [1], "2030612629": [1], "2030652630": [1], "2030965798": [1], "2031539502": [1], "2032904832": [1], "2032927450": [1], "2033166303": [1], "2033391370": [1], "2033902275": [1], "2034133936": [1], "2034151409": [1], "2034854593": [1], "2035042453": [1], "2035600660": [1], "2035868431": [1], "2035951895": [1], "2036949401": [1], "2037238619": [1], "2037524848": [1], "2037531237": [1], "2037546839": [1], "2037654146": [1], "2037893305": [1], "2037940607": [1], "2038073953": [1], "2038293587": [1], "2038571448": [1], "2038908953": [1], "2039478130": [1], "2039553671": [1], "2039735007": [1], "2039740510": [1], "2039827958": [1], "2039986714": [1], "2040033014": [1], "2040305266": [1], "2040615996": [1], "2040952592": [1], "2041523340": [1], "2041551907": [1], "2042214781": [1], "2042373885": [1], "2042850797": [1], "2043070984": [1], "2043480204": [1], "2043510314": [1], "2043892436": [1], "2043935568": [1], "2045344399": [1], "2045347884": [1], "2045636363": [1], "2045706236": [1], "2045969039": [1], "2046187786": [1], "2046257463": [1], "2046353527": [1], "2046403536": [1], "2046844005": [1], "2047096758": [1], "2047153072": [1], "2047221573": [1], "2047401783": [1], "2047480308": [1], "2047842876": [1], "2048651980": [1], "2048682331": [1], "2048814841": [1], "2048817369": [1], "2048985443": [1], "2049192084": [1], "2049237050": [1], "2049433607": [1], "2049503826": [1], "2049600555": [1], "2050086393": [1], "2050552970": [1], "2051084552": [1], "2051445648": [1], "2051678551": [1], "2051954974": [1], "2052813967": [1], "2053105352": [1], "2053587384": [1], "2053767132": [1], "2053834098": [1], "2054133042": [1], "2054194366": [1], "2054681553": [1], "2054705763": [1], "2054836175": [1], "2054907250": [1], "2055418078": [1], "2055581632": [1], "2055586551": [1], "2055813378": [1], "2055842215": [1], "2055880494": [1], "2056217468": [1], "2056299493": [1], "2056318180": [1], "2056407534": [1], "2056785371": [1], "2056899192": [1], "2057116224": [1], "2057218843": [1], "2057671664": [1], "2057727506": [1], "2057732187": [1], "2057798640": [1], "2057980379": [1], "2058095539": [1], "2059050922": [1], "2059607139": [1], "2059804041": [1], "2060208057": [1], "2060431439": [1], "2060799009": [1], "2061120426": [1], "2061129144": [1], "2061172951": [1], "2061562820": [1], "2061705261": [1], "2061868712": [1], "2062077567": [1], "2062121270": [1], "2062126348": [1], "2062316065": [1], "2062355256": [1], "2062720548": [1], "2062724588": [1], "2062765814": [1], "2062894426": [1], "2063255796": [1], "2063393888": [1], "2063511622": [1], "2063700030": [1], "2064005671": [1], "2064218423": [1], "2064241041": [1], "2064258925": [1], "2064525878": [1], "2064556328": [1], "2064682483": [1], "2064851700": [1], "2065120126": [1], "2066078047": [1], "2066716707": [1], "2066799957": [1], "2067512419": [1], "2068209776": [1], "2068218166": [1], "2068297898": [1], "2068565097": [1], "2068643972": [1], "2068733038": [1], "2069185191": [1], "2069389938": [1], "2069725387": [1], "2070011324": [1], "2070125916": [1], "2070311689": [1], "2071404650": [1], "2071871513": [1], "2072596639": [1], "2072684566": [1], "2072804053": [1], "2072840139": [1], "2072857293": [1], "2072914240": [1], "2073480556": [1], "2073483301": [1], "2074069419": [1], "2074158716": [1], "2074687179": [1], "2074988730": [1], "2075257762": [1], "2075346061": [1], "2075486055": [1], "2075594956": [1], "2075708831": [1], "2075824271": [1], "2076001938": [1], "2076573554": [1], "2076962925": [1], "2076974035": [1], "2077443955": [1], "2078311650": [1], "2078552158": [1], "2078654055": [1], "2078725686": [1], "2078781324": [1], "2078803849": [1], "2079094967": [1], "2079213442": [1], "2079433685": [1], "2079495017": [1], "2080367385": [1], "2080651269": [1], "2080653109": [1], "2080796509": [1], "2080825286": [1], "2081275578": [1], "2081350169": [1], "2081558848": [1], "2081941299": [1], "2082023531": [1], "2082270168": [1], "2082987184": [1], "2082997895": [1], "2083947380": [1], "2084231778": [1], "2084377201": [1], "2085023729": [1], "2085177070": [1], "2085247838": [1], "2085564593": [1], "2085891632": [1], "2086185676": [1], "2086284780": [1], "2086448185": [1], "2086895395": [1], "2087829145": [1], "2088451487": [1], "2088470766": [1], "2089685891": [1], "2089744488": [1], "2089853735": [1], "2089936579": [1], "2090003937": [1], "2090362715": [1], "2090826753": [1], "2090987762": [1], "2091087869": [1], "2091128422": [1], "2091152875": [1], "2091467863": [1], "2091481100": [1], "2091890663": [1], "2092397452": [1], "2092514951": [1], "2092519729": [1], "2092624117": [1], "2092630208": [1], "2092935924": [1], "2093185671": [1], "2093505679": [1], "2094207587": [1], "2094350886": [1], "2094414634": [1], "2094587446": [1], "2095052789": [1], "2095524097": [1], "2096116625": [1], "2096607763": [1], "2096635041": [1], "2096925586": [1], "2097174465": [1], "2097216490": [1], "2097235337": [1], "2097439377": [1], "2097548359": [1], "2098132172": [1], "2098335008": [1], "2098426150": [1], "2098645800": [1], "2098971440": [1], "2100248965": [1], "2100285249": [1], "2100287459": [1], "2100300212": [1], "2100598395": [1], "2100598956": [1], "2101231048": [1], "2101485456": [1], "2101830516": [1], "2102061800": [1], "2102450486": [1], "2102503575": [1], "2102518900": [1], "2102764928": [1], "2103063416": [1], "2103081120": [1], "2103575230": [1], "2103983012": [1], "2103998845": [1], "2104360530": [1], "2104809719": [1], "2104841360": [1], "2105226605": [1], "2105296437": [1], "2105562737": [1], "2106033497": [1], "2106302237": [1], "2106334233": [1], "2106377384": [1], "2106433173": [1], "2107080625": [1], "2107503392": [1], "2107900584": [1], "2108538687": [1], "2108564348": [1], "2109069089": [1], "2109118288": [1], "2109126820": [1], "2109193299": [1], "2109301240": [1], "2109377434": [1], "2109538623": [1], "2109690647": [1], "2109839097": [1], "2110230167": [1], "2110633757": [1], "2111002018": [1], "2112307468": [1], "2112483985": [1], "2112913210": [1], "2112915449": [1], "2112919708": [1], "2113086424": [1], "2113121766": [1], "2113269557": [1], "2113494878": [1], "2113864540": [1], "2114094733": [1], "2114509404": [1], "2114745670": [1], "2114792361": [1], "2114914452": [1], "2115389524": [1], "2115564258": [1], "2115627472": [1], "2116055653": [1], "2116696564": [1], "2116903872": [1], "2116914355": [1], "2117054738": [1], "2117081596": [1], "2117198122": [1], "2117398976": [1], "2117538586": [1], "2118297306": [1], "2118528652": [1], "2118697622": [1], "2118856858": [1], "2118900105": [1], "2119009574": [1], "2119213605": [1], "2119394213": [1], "2119723882": [1], "2119924234": [1], "2120040924": [1], "2120051143": [1], "2120221285": [1], "2121150757": [1], "2121197739": [1], "2122014066": [1], "2122199282": [1], "2122228970": [1], "2122489886": [1], "2122586611": [1], "2122616018": [1], "2123589494": [1], "2123780144": [1], "2123808272": [1], "2124234719": [1], "2124581745": [1], "2124747906": [1], "2124796333": [1], "2125035188": [1], "2125041812": [1], "2125079672": [1], "2125093435": [1], "2125276444": [1], "2125543146": [1], "2125612826": [1], "2126013408": [1], "2126286708": [1], "2126288801": [1], "2126598686": [1], "2126712783": [1], "2126756783": [1], "2127724644": [1], "2127936809": [1], "2128056411": [1], "2128058759": [1], "2128172412": [1], "2129317152": [1], "2129900312": [1], "2130023780": [1], "2130627394": [1], "2130867980": [1], "2131043084": [1], "2131452599": [1], "2131665205": [1], "2131786304": [1], "2131844464": [1], "2131941799": [1], "2132093137": [1], "2132415333": [1], "2132979146": [1], "2133131401": [1], "2133188739": [1], "2133354654": [1], "2133392499": [1], "2133448405": [1], "2133458686": [1], "2133487634": [1], "2133734946": [1], "2134389409": [1], "2134724644": [1], "2134889089": [1], "2135152917": [1], "2135205642": [1], "2135744331": [1], "2135752791": [1], "2135802817": [1], "2136150346": [1], "2137062190": [1], "2137163172": [1], "2137190467": [1], "2137460427": [1], "2137726037": [1], "2138140159": [1], "2138537726": [1], "2138601301": [1], "2139509731": [1], "2139517217": [1], "2140335082": [1], "2140660332": [1], "2140934070": [1], "2141531399": [1], "2141620489": [1], "2141679926": [1], "2142430174": [1], "2142520268": [1], "2143331426": [1], "2143789725": [1], "2143815146": [1], "2144228550": [1], "2144445010": [1], "2144731341": [1], "2144788535": [1], "2144944633": [1], "2145097875": [1], "2145101307": [1], "2145402105": [1], "2145950221": [1], "2146923977": [1], "2146954744": [1], "2147094670": [1], "2147109152": [1], "2147476872": [1]}, "references": [{"name": "ebola-sudan", "length": 18875, "nMinimizers": 9481}, {"name": "ebola-zaire", "length": 18959, "nMinimizers": 9443}], "normalization": [1.9908237527686954, 2.007730594090861]} \ No newline at end of file diff --git a/preprocessing/nextclade/tests/factory_methods.py b/preprocessing/nextclade/tests/factory_methods.py index 5b566645a3..82530b49f4 100644 --- a/preprocessing/nextclade/tests/factory_methods.py +++ b/preprocessing/nextclade/tests/factory_methods.py @@ -46,7 +46,7 @@ class ProcessingAnnotationHelper: @dataclass class ProcessedAlignment: unalignedNucleotideSequences: dict[str, str | None] = field( # noqa: N815 - default_factory=lambda: {"main": None} + default_factory=dict ) alignedNucleotideSequences: dict[str, str | None] = field( # noqa: N815 default_factory=dict @@ -54,6 +54,9 @@ class ProcessedAlignment: nucleotideInsertions: dict[str, list[str]] = field(default_factory=dict) # noqa: N815 alignedAminoAcidSequences: dict[str, str | None] = field(default_factory=dict) # noqa: N815 aminoAcidInsertions: dict[str, list[str]] = field(default_factory=dict) # noqa: N815 + sequenceNameToFastaHeaderMap: dict[str, str] = field( # noqa: N815 + default_factory=dict + ) @dataclass @@ -137,6 +140,7 @@ def create_processed_entry( nucleotideInsertions=processed_alignment.nucleotideInsertions, alignedAminoAcidSequences=processed_alignment.alignedAminoAcidSequences, aminoAcidInsertions=processed_alignment.aminoAcidInsertions, + sequenceNameToFastaHeaderMap=processed_alignment.sequenceNameToFastaHeaderMap, ), errors=errors, warnings=warnings, @@ -243,3 +247,7 @@ def verify_processed_entry( f"{test_name}: amino acid insertions '{actual.aminoAcidInsertions}' do not " f"match expectation '{expected.aminoAcidInsertions}'." ) + assert actual.sequenceNameToFastaHeaderMap == expected.sequenceNameToFastaHeaderMap, ( + f"{test_name}: sequence name to fasta header map '{actual.sequenceNameToFastaHeaderMap}' do not " + f"match expectation '{expected.sequenceNameToFastaHeaderMap}'." + ) diff --git a/preprocessing/nextclade/tests/multi_pathogen_config.yaml b/preprocessing/nextclade/tests/multi_pathogen_config.yaml index f84eaaaa0c..9a8a6f13e5 100644 --- a/preprocessing/nextclade/tests/multi_pathogen_config.yaml +++ b/preprocessing/nextclade/tests/multi_pathogen_config.yaml @@ -7,11 +7,15 @@ genes: - VP24EbolaZaire - LEbolaZaire log_level: DEBUG -nextclade_dataset_name: ebola-dataset -nextclade_dataset_server: fake_url +minimizer_index: TEST +nextclade_dataset_server: TEST nucleotideSequences: -- ebola-sudan -- ebola-zaire +- name: ebola-sudan + nextclade_dataset_name: ebola-dataset/ebola-sudan + accepted_sort_matches: ebola-sudan +- name: ebola-zaire + nextclade_dataset_name: ebola-dataset/ebola-zaire + accepted_sort_matches: ebola-zaire organism: multi-ebola-test processing_spec: totalSnps: diff --git a/preprocessing/nextclade/tests/multi_segment_config.yaml b/preprocessing/nextclade/tests/multi_segment_config.yaml index bfac8b94e7..eb4d5099d7 100644 --- a/preprocessing/nextclade/tests/multi_segment_config.yaml +++ b/preprocessing/nextclade/tests/multi_segment_config.yaml @@ -6,11 +6,15 @@ genes: - VP24EbolaZaire - LEbolaZaire log_level: DEBUG -nextclade_dataset_name: ebola-dataset -nextclade_dataset_server: fake_url +minimizer_index: TEST +nextclade_dataset_server: TEST nucleotideSequences: -- ebola-sudan -- ebola-zaire +- name: ebola-sudan + nextclade_dataset_name: ebola-dataset/ebola-sudan + accepted_sort_matches: ebola-sudan +- name: ebola-zaire + nextclade_dataset_name: ebola-dataset/ebola-zaire + accepted_sort_matches: ebola-zaire organism: multi-ebola-test processing_spec: totalInsertedNucs_ebola-zaire: diff --git a/preprocessing/nextclade/tests/multi_segment_config_unaligned.yaml b/preprocessing/nextclade/tests/multi_segment_config_unaligned.yaml new file mode 100644 index 0000000000..bc82c15079 --- /dev/null +++ b/preprocessing/nextclade/tests/multi_segment_config_unaligned.yaml @@ -0,0 +1,23 @@ +batch_size: 100 +alignment_requirement: NONE +log_level: DEBUG +minimizer_index: TEST +nucleotideSequences: +- name: ebola-sudan +- name: ebola-zaire +organism: multi-ebola-test +processing_spec: + length_ebola-sudan: + args: + segment: ebola-sudan + type: int + function: identity + inputs: + input: length_ebola-sudan + length_ebola-zaire: + args: + segment: ebola-zaire + type: int + function: identity + inputs: + input: length_ebola-zaire diff --git a/preprocessing/nextclade/tests/single_segment_config.yaml b/preprocessing/nextclade/tests/single_segment_config.yaml index 91bf969b89..e847abda73 100644 --- a/preprocessing/nextclade/tests/single_segment_config.yaml +++ b/preprocessing/nextclade/tests/single_segment_config.yaml @@ -3,14 +3,14 @@ genes: - NPEbolaSudan - VP35EbolaSudan log_level: DEBUG -nextclade_dataset_name: ebola-sudan-test-dataset -nextclade_dataset_server: fake_url +nextclade_dataset_server: TEST scientific_name: "Test Ebola Sudan Virus" molecule_type: "genomic RNA" topology: "linear" db_name: "Loculus" +minimizer_index: TEST nucleotideSequences: -- main +- nextclade_dataset_name: ebola-sudan-test-dataset organism: ebola-sudan-test processing_spec: completeness: diff --git a/preprocessing/nextclade/tests/test_nextclade_preprocessing.py b/preprocessing/nextclade/tests/test_nextclade_preprocessing.py index b6c196ee95..b9b40b7341 100644 --- a/preprocessing/nextclade/tests/test_nextclade_preprocessing.py +++ b/preprocessing/nextclade/tests/test_nextclade_preprocessing.py @@ -38,6 +38,7 @@ # Config file used for testing SINGLE_SEGMENT_CONFIG = "tests/single_segment_config.yaml" MULTI_SEGMENT_CONFIG = "tests/multi_segment_config.yaml" +MULTI_SEGMENT_CONFIG_UNALIGNED = "tests/multi_segment_config_unaligned.yaml" MULTI_PATHOGEN_CONFIG = "tests/multi_pathogen_config.yaml" EMBL_METADATA = "tests/embl_required_metadata.yaml" @@ -119,7 +120,7 @@ def invalid_sequence() -> str: Case( name="with mutation", input_metadata={}, - input_sequence={"main": sequence_with_mutation("single")}, + input_sequence={"fastaHeader": sequence_with_mutation("single")}, accession_id="1", expected_metadata={ "completeness": 1.0, @@ -140,12 +141,13 @@ def invalid_sequence() -> str: "VP35EbolaSudan": ebola_sudan_aa(sequence_with_mutation("single"), "VP35"), }, aminoAcidInsertions={}, + sequenceNameToFastaHeaderMap={"main": "fastaHeader"}, ), ), Case( name="with insertion", input_metadata={}, - input_sequence={"main": sequence_with_insertion("single")}, + input_sequence={"fastaHeader": sequence_with_insertion("single")}, accession_id="1", expected_metadata={ "completeness": 1.0, @@ -166,12 +168,13 @@ def invalid_sequence() -> str: "VP35EbolaSudan": ebola_sudan_aa(consensus_sequence("single"), "VP35"), }, aminoAcidInsertions={"NPEbolaSudan": ["738:D"]}, + sequenceNameToFastaHeaderMap={"main": "fastaHeader"}, ), ), Case( name="with deletion", input_metadata={}, - input_sequence={"main": sequence_with_deletion("single")}, + input_sequence={"fastaHeader": sequence_with_deletion("single")}, accession_id="1", expected_metadata={ "completeness": 1.0, @@ -196,12 +199,13 @@ def invalid_sequence() -> str: ), }, aminoAcidInsertions={}, + sequenceNameToFastaHeaderMap={"main": "fastaHeader"}, ), ), Case( name="with failed alignment", input_metadata={}, - input_sequence={"main": invalid_sequence()}, + input_sequence={"fastaHeader": invalid_sequence()}, accession_id="1", expected_metadata={ "completeness": None, @@ -224,10 +228,11 @@ def invalid_sequence() -> str: expected_warnings=[], expected_processed_alignment=ProcessedAlignment( unalignedNucleotideSequences={"main": invalid_sequence()}, - alignedNucleotideSequences={"main": None}, + alignedNucleotideSequences={}, nucleotideInsertions={}, alignedAminoAcidSequences={}, aminoAcidInsertions={}, + sequenceNameToFastaHeaderMap={"main": "fastaHeader"}, ), ), ] @@ -237,8 +242,8 @@ def invalid_sequence() -> str: name="with mutation", input_metadata={}, input_sequence={ - "ebola-sudan": sequence_with_mutation("ebola-sudan"), - "ebola-zaire": sequence_with_mutation("ebola-zaire"), + "fastaHeader1": sequence_with_mutation("ebola-sudan"), + "fastaHeader2": sequence_with_mutation("ebola-zaire"), }, accession_id="1", expected_metadata={ @@ -270,14 +275,18 @@ def invalid_sequence() -> str: "LEbolaZaire": ebola_zaire_aa(sequence_with_mutation("ebola-zaire"), "L"), }, aminoAcidInsertions={}, + sequenceNameToFastaHeaderMap={ + "ebola-sudan": "fastaHeader1", + "ebola-zaire": "fastaHeader2", + }, ), ), Case( name="with insertion", input_metadata={}, input_sequence={ - "ebola-sudan": sequence_with_insertion("ebola-sudan"), - "ebola-zaire": sequence_with_insertion("ebola-zaire"), + "fastaHeader1": sequence_with_insertion("ebola-sudan"), + "fastaHeader2": sequence_with_insertion("ebola-zaire"), }, accession_id="1", expected_metadata={ @@ -309,14 +318,18 @@ def invalid_sequence() -> str: "LEbolaZaire": ebola_zaire_aa(consensus_sequence("ebola-zaire"), "L"), }, aminoAcidInsertions={"NPEbolaSudan": ["738:D"], "VP24EbolaZaire": ["251:D"]}, + sequenceNameToFastaHeaderMap={ + "ebola-sudan": "fastaHeader1", + "ebola-zaire": "fastaHeader2", + }, ), ), Case( name="with deletion", input_metadata={}, input_sequence={ - "ebola-sudan": sequence_with_deletion("ebola-sudan"), - "ebola-zaire": sequence_with_deletion("ebola-zaire"), + "fastaHeader1": sequence_with_deletion("ebola-sudan"), + "fastaHeader2": sequence_with_deletion("ebola-zaire"), }, accession_id="1", expected_metadata={ @@ -358,13 +371,17 @@ def invalid_sequence() -> str: ), }, aminoAcidInsertions={}, + sequenceNameToFastaHeaderMap={ + "ebola-sudan": "fastaHeader1", + "ebola-zaire": "fastaHeader2", + }, ), ), Case( name="with one succeeded and one not uploaded", input_metadata={}, input_sequence={ - "ebola-zaire": sequence_with_mutation("ebola-zaire"), + "fastaHeader2": sequence_with_mutation("ebola-zaire"), }, accession_id="1", expected_metadata={ @@ -392,6 +409,7 @@ def invalid_sequence() -> str: "LEbolaZaire": ebola_zaire_aa(sequence_with_mutation("ebola-zaire"), "L"), }, aminoAcidInsertions={}, + sequenceNameToFastaHeaderMap={"ebola-zaire": "fastaHeader2"}, ), ), ] @@ -400,13 +418,13 @@ def invalid_sequence() -> str: Case( name="with one failed alignment, one not uploaded", input_metadata={}, - input_sequence={"ebola-sudan": invalid_sequence()}, + input_sequence={"fastaHeader1": invalid_sequence()}, accession_id="1", expected_metadata={ "totalInsertedNucs_ebola-sudan": None, "totalSnps_ebola-sudan": None, "totalDeletedNucs_ebola-sudan": None, - "length_ebola-sudan": 53, + "length_ebola-sudan": 0, "totalInsertedNucs_ebola-zaire": None, "totalSnps_ebola-zaire": None, "totalDeletedNucs_ebola-zaire": None, @@ -415,37 +433,36 @@ def invalid_sequence() -> str: expected_errors=build_processing_annotations( [ ProcessingAnnotationHelper( - ["ebola-sudan"], - ["ebola-sudan"], - "Nucleotide sequence for ebola-sudan failed to align", + ["alignment"], + ["alignment"], + "Sequence with fasta header fastaHeader1 does not appear to match any reference for organism: multi-ebola-test per `nextclade sort`. Double check you are submitting to the correct organism.", AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - ), + ) ] ), expected_warnings=[], expected_processed_alignment=ProcessedAlignment( - unalignedNucleotideSequences={ - "ebola-sudan": invalid_sequence(), - }, - alignedNucleotideSequences={"ebola-sudan": None}, + unalignedNucleotideSequences={}, + alignedNucleotideSequences={}, nucleotideInsertions={}, alignedAminoAcidSequences={}, aminoAcidInsertions={}, + sequenceNameToFastaHeaderMap={}, ), ), Case( name="with one failed alignment, one succeeded", input_metadata={}, input_sequence={ - "ebola-sudan": invalid_sequence(), - "ebola-zaire": sequence_with_mutation("ebola-zaire"), + "fastaHeader1": invalid_sequence(), + "fastaHeader2": sequence_with_mutation("ebola-zaire"), }, accession_id="1", expected_metadata={ "totalInsertedNucs_ebola-sudan": None, "totalSnps_ebola-sudan": None, "totalDeletedNucs_ebola-sudan": None, - "length_ebola-sudan": 53, + "length_ebola-sudan": 0, "totalInsertedNucs_ebola-zaire": 0, "totalSnps_ebola-zaire": 1, "totalDeletedNucs_ebola-zaire": 0, @@ -454,9 +471,9 @@ def invalid_sequence() -> str: expected_errors=build_processing_annotations( [ ProcessingAnnotationHelper( - ["ebola-sudan"], - ["ebola-sudan"], - "Nucleotide sequence for ebola-sudan failed to align", + ["alignment"], + ["alignment"], + "Sequence with fasta header fastaHeader1 does not appear to match any reference for organism: multi-ebola-test per `nextclade sort`. Double check you are submitting to the correct organism.", AnnotationSourceType.NUCLEOTIDE_SEQUENCE, ), ] @@ -464,11 +481,9 @@ def invalid_sequence() -> str: expected_warnings=[], expected_processed_alignment=ProcessedAlignment( unalignedNucleotideSequences={ - "ebola-sudan": invalid_sequence(), "ebola-zaire": sequence_with_mutation("ebola-zaire"), }, alignedNucleotideSequences={ - "ebola-sudan": None, "ebola-zaire": sequence_with_mutation("ebola-zaire"), }, nucleotideInsertions={}, @@ -477,6 +492,7 @@ def invalid_sequence() -> str: "LEbolaZaire": ebola_zaire_aa(sequence_with_mutation("ebola-zaire"), "L"), }, aminoAcidInsertions={}, + sequenceNameToFastaHeaderMap={"ebola-zaire": "fastaHeader2"}, ), ), ] @@ -485,13 +501,13 @@ def invalid_sequence() -> str: Case( name="with one failed alignment, one not uploaded", input_metadata={}, - input_sequence={"ebola-sudan": invalid_sequence()}, + input_sequence={"fastaHeader1": invalid_sequence()}, accession_id="1", expected_metadata={ "totalInsertedNucs_ebola-sudan": None, "totalSnps_ebola-sudan": None, "totalDeletedNucs_ebola-sudan": None, - "length_ebola-sudan": 53, + "length_ebola-sudan": 0, "totalInsertedNucs_ebola-zaire": None, "totalSnps_ebola-zaire": None, "totalDeletedNucs_ebola-zaire": None, @@ -502,7 +518,7 @@ def invalid_sequence() -> str: ProcessingAnnotationHelper( [ProcessingAnnotationAlignment], [ProcessingAnnotationAlignment], - "No segment aligned.", + "No sequence data could be classified - check you are submitting to the correct organism.", AnnotationSourceType.NUCLEOTIDE_SEQUENCE, ) ] @@ -510,36 +526,35 @@ def invalid_sequence() -> str: expected_warnings=build_processing_annotations( [ ProcessingAnnotationHelper( - ["ebola-sudan"], - ["ebola-sudan"], - "Nucleotide sequence for ebola-sudan failed to align", + ["alignment"], + ["alignment"], + "Sequence with fasta header fastaHeader1 does not appear to match any reference for organism: multi-ebola-test per `nextclade sort`. Double check you are submitting to the correct organism.", AnnotationSourceType.NUCLEOTIDE_SEQUENCE, ) ] ), expected_processed_alignment=ProcessedAlignment( - unalignedNucleotideSequences={ - "ebola-sudan": invalid_sequence(), - }, - alignedNucleotideSequences={"ebola-sudan": None}, + unalignedNucleotideSequences={}, + alignedNucleotideSequences={}, nucleotideInsertions={}, alignedAminoAcidSequences={}, aminoAcidInsertions={}, + sequenceNameToFastaHeaderMap={}, ), ), Case( name="with one failed alignment, one succeeded", input_metadata={}, input_sequence={ - "ebola-sudan": invalid_sequence(), - "ebola-zaire": sequence_with_mutation("ebola-zaire"), + "fastaHeader1": invalid_sequence(), + "fastaHeader2": sequence_with_mutation("ebola-zaire"), }, accession_id="1", expected_metadata={ "totalInsertedNucs_ebola-sudan": None, "totalSnps_ebola-sudan": None, "totalDeletedNucs_ebola-sudan": None, - "length_ebola-sudan": 53, + "length_ebola-sudan": 0, "totalInsertedNucs_ebola-zaire": 0, "totalSnps_ebola-zaire": 1, "totalDeletedNucs_ebola-zaire": 0, @@ -549,20 +564,18 @@ def invalid_sequence() -> str: expected_warnings=build_processing_annotations( [ ProcessingAnnotationHelper( - ["ebola-sudan"], - ["ebola-sudan"], - "Nucleotide sequence for ebola-sudan failed to align", + ["alignment"], + ["alignment"], + "Sequence with fasta header fastaHeader1 does not appear to match any reference for organism: multi-ebola-test per `nextclade sort`. Double check you are submitting to the correct organism.", AnnotationSourceType.NUCLEOTIDE_SEQUENCE, ) ] ), expected_processed_alignment=ProcessedAlignment( unalignedNucleotideSequences={ - "ebola-sudan": invalid_sequence(), "ebola-zaire": sequence_with_mutation("ebola-zaire"), }, alignedNucleotideSequences={ - "ebola-sudan": None, "ebola-zaire": sequence_with_mutation("ebola-zaire"), }, nucleotideInsertions={}, @@ -571,6 +584,176 @@ def invalid_sequence() -> str: "LEbolaZaire": ebola_zaire_aa(sequence_with_mutation("ebola-zaire"), "L"), }, aminoAcidInsertions={}, + sequenceNameToFastaHeaderMap={"ebola-zaire": "fastaHeader2"}, + ), + ), +] + +segment_validation_tests_single_segment = [ + Case( + name="do not accept multiple segments for single segment", + input_metadata={}, + input_sequence={ + "fastaHeader1": sequence_with_mutation("single"), + "fastaHeader2": sequence_with_mutation("single"), + }, + accession_id="2", + expected_metadata={"length": 0}, + expected_errors=build_processing_annotations([ + ProcessingAnnotationHelper( + [ProcessingAnnotationAlignment], + [ProcessingAnnotationAlignment], + "Multiple sequences: ['fastaHeader1', 'fastaHeader2'] found in the" + " input data, but organism: ebola-sudan-test is single-segmented. " + "Please check that your metadata and sequences are annotated correctly." + "Each metadata entry should have a single corresponding fasta sequence " + "entry with the same submissionId.", + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + ), + ]), + expected_warnings=[], + expected_processed_alignment=ProcessedAlignment( + unalignedNucleotideSequences={}, + alignedNucleotideSequences={}, + nucleotideInsertions={}, + alignedAminoAcidSequences={}, + aminoAcidInsertions={}, + sequenceNameToFastaHeaderMap={}, + ), + ), +] + +segment_validation_tests_multi_segments = [ + Case( + name="don't allow duplicated of the same segment", + input_metadata={}, + input_sequence={ + "ebola-sudan": sequence_with_mutation("ebola-sudan"), + "duplicate_ebola-sudan": sequence_with_mutation("ebola-sudan"), + }, + accession_id="1", + expected_metadata={ + "totalInsertedNucs_ebola-sudan": None, + "totalSnps_ebola-sudan": None, + "totalDeletedNucs_ebola-sudan": None, + "length_ebola-sudan": 0, + "totalInsertedNucs_ebola-zaire": None, + "totalSnps_ebola-zaire": None, + "totalDeletedNucs_ebola-zaire": None, + "length_ebola-zaire": 0, + }, + expected_errors=build_processing_annotations([ + ProcessingAnnotationHelper( + [ProcessingAnnotationAlignment], + [ProcessingAnnotationAlignment], + "Multiple sequences (with fasta headers: duplicate_ebola-sudan, ebola-sudan) align to ebola-sudan - only one entry is allowed.", + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + ), + ]), + expected_warnings=[], + expected_processed_alignment=ProcessedAlignment( + unalignedNucleotideSequences={}, + alignedNucleotideSequences={}, + nucleotideInsertions={}, + alignedAminoAcidSequences={}, + aminoAcidInsertions={}, + ), + ), +] + +multi_segment_case_definitions_none_requirement = [ + Case( + name="accept any prefix for multi-segment", + input_metadata={}, + input_sequence={ + "prefix_ebola-sudan": sequence_with_mutation("ebola-sudan"), + "other_prefix_ebola-zaire": sequence_with_mutation("ebola-zaire"), + }, + accession_id="1", + expected_metadata={ + "length_ebola-sudan": len(consensus_sequence("ebola-sudan")), + "length_ebola-zaire": len(consensus_sequence("ebola-zaire")), + }, + expected_errors=[], + expected_warnings=[], + expected_processed_alignment=ProcessedAlignment( + unalignedNucleotideSequences={ + "ebola-sudan": sequence_with_mutation("ebola-sudan"), + "ebola-zaire": sequence_with_mutation("ebola-zaire"), + }, + alignedNucleotideSequences={}, + nucleotideInsertions={}, + alignedAminoAcidSequences={}, + aminoAcidInsertions={}, + sequenceNameToFastaHeaderMap={ + "ebola-sudan": "prefix_ebola-sudan", + "ebola-zaire": "other_prefix_ebola-zaire", + }, + ), + ), + Case( + name="don't allow multiple segments with the same name", + input_metadata={}, + input_sequence={ + "ebola-sudan": invalid_sequence(), + "duplicate_ebola-sudan": invalid_sequence(), + }, + accession_id="1", + expected_metadata={ + "length_ebola-sudan": 0, + "length_ebola-zaire": 0, + }, + expected_errors=build_processing_annotations([ + ProcessingAnnotationHelper( + [ProcessingAnnotationAlignment], + [ProcessingAnnotationAlignment], + "Found multiple sequences with the same segment name: ebola-sudan. " + "Each metadata entry can have multiple corresponding fasta sequence " + "entries with format _.", + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + ) + ]), + expected_warnings=[], + expected_processed_alignment=ProcessedAlignment( + unalignedNucleotideSequences={}, + alignedNucleotideSequences={}, + nucleotideInsertions={}, + alignedAminoAcidSequences={}, + aminoAcidInsertions={}, + sequenceNameToFastaHeaderMap={}, + ), + ), + Case( + name="don't allow unknown segments", + input_metadata={}, + input_sequence={ + "ebola-sudan": sequence_with_mutation("ebola-sudan"), + "unknown_segment": invalid_sequence(), + }, + accession_id="2", + expected_metadata={ + "length_ebola-sudan": len(consensus_sequence("ebola-sudan")), + "length_ebola-zaire": 0, + }, + expected_errors=build_processing_annotations([ + ProcessingAnnotationHelper( + [ProcessingAnnotationAlignment], + [ProcessingAnnotationAlignment], + "Found sequences in the input data with segments that are not in the config: " + "unknown_segment. Each metadata entry can have multiple corresponding fasta sequence " + "entries with format _ valid segments are: " + "ebola-sudan, ebola-zaire.", + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + ) + ]), + expected_warnings=[], + expected_processed_alignment=ProcessedAlignment( + unalignedNucleotideSequences={"ebola-sudan": sequence_with_mutation("ebola-sudan")}, + alignedNucleotideSequences={}, + nucleotideInsertions={}, + alignedAminoAcidSequences={}, + aminoAcidInsertions={}, + sequenceNameToFastaHeaderMap={"ebola-sudan": "ebola-sudan"}, ), ), ] @@ -584,7 +767,9 @@ def process_single_entry( @pytest.mark.parametrize( - "test_case_def", single_segment_case_definitions, ids=lambda tc: f"single segment {tc.name}" + "test_case_def", + single_segment_case_definitions + segment_validation_tests_single_segment, + ids=lambda tc: f"single segment {tc.name}", ) def test_preprocessing_single_segment(test_case_def: Case): config = get_config(SINGLE_SEGMENT_CONFIG, ignore_args=True) @@ -598,7 +783,9 @@ def test_preprocessing_single_segment(test_case_def: Case): @pytest.mark.parametrize( "test_case_def", - multi_segment_case_definitions + multi_segment_case_definitions_all_requirement, + multi_segment_case_definitions + + segment_validation_tests_multi_segments + + multi_segment_case_definitions_all_requirement, ids=lambda tc: f"multi segment {tc.name}", ) def test_preprocessing_multi_segment_all_requirement(test_case_def: Case): @@ -613,7 +800,9 @@ def test_preprocessing_multi_segment_all_requirement(test_case_def: Case): @pytest.mark.parametrize( "test_case_def", - multi_segment_case_definitions + multi_segment_case_definitions_any_requirement, + multi_segment_case_definitions + + segment_validation_tests_multi_segments + + multi_segment_case_definitions_any_requirement, ids=lambda tc: f"multi segment {tc.name}", ) def test_preprocessing_multi_segment_any_requirement(test_case_def: Case): @@ -627,6 +816,21 @@ def test_preprocessing_multi_segment_any_requirement(test_case_def: Case): ) +@pytest.mark.parametrize( + "test_case_def", + multi_segment_case_definitions_none_requirement, + ids=lambda tc: f"multi segment not aligned {tc.name}", +) +def test_preprocessing_multi_segment_none_requirement(test_case_def: Case): + config = get_config(MULTI_SEGMENT_CONFIG_UNALIGNED, ignore_args=True) + factory_custom = ProcessedEntryFactory(all_metadata_fields=list(config.processing_spec.keys())) + test_case = test_case_def.create_test_case(factory_custom) + processed_entry = process_single_entry(test_case, config) + verify_processed_entry( + processed_entry.processed_entry, test_case.expected_output, test_case.name + ) + + def test_preprocessing_without_metadata() -> None: config = get_config(MULTI_SEGMENT_CONFIG, ignore_args=True) sequence_entry_data = UnprocessedEntry( @@ -762,6 +966,7 @@ def test_create_flatfile(): "LEbolaZaire": ebola_zaire_aa(sequence_with_mutation("ebola-zaire"), "L"), }, aminoAcidInsertions={}, + sequenceNameToFastaHeaderMap={"ebola-zaire": "ebola-zaire"}, ), ), Case( @@ -791,6 +996,7 @@ def test_create_flatfile(): "VP35EbolaSudan": ebola_sudan_aa(sequence_with_mutation("single"), "VP35"), }, aminoAcidInsertions={}, + sequenceNameToFastaHeaderMap={"ebola-sudan": "ebola-sudan"}, ), ), Case( @@ -854,6 +1060,10 @@ def test_create_flatfile(): "LEbolaZaire": ebola_zaire_aa(sequence_with_mutation("ebola-zaire"), "L"), }, aminoAcidInsertions={}, + sequenceNameToFastaHeaderMap={ + "ebola-sudan": "ebola-sudan", + "ebola-zaire": "ebola-zaire", + }, ), ), ] diff --git a/preprocessing/specification.md b/preprocessing/specification.md index 00d4ffb645..07532a2f9a 100644 --- a/preprocessing/specification.md +++ b/preprocessing/specification.md @@ -92,6 +92,7 @@ In the NDJSON, each row contains a sequence entry version and a list of errors a nucleotideInsertions, alignedAminoAcidSequences, aminoAcidInsertions, + sequenceNameToFastaHeaderMap, //TODO: use in backend files } } diff --git a/website/src/components/Edit/EditPage.tsx b/website/src/components/Edit/EditPage.tsx index f9e934b00e..812c1e657e 100644 --- a/website/src/components/Edit/EditPage.tsx +++ b/website/src/components/Edit/EditPage.tsx @@ -77,7 +77,12 @@ const InnerEditPage: FC = ({ const submitEditedDataForAccessionVersion = () => { if (isCreatingRevision) { - const metadataFile = editableMetadata.getMetadataTsv(dataToEdit.submissionId, dataToEdit.accession); + const fastaId = submissionDataTypes.consensusSequences ? editableSequences.getFastaIds() : undefined; + const metadataFile = editableMetadata.getMetadataTsv( + dataToEdit.submissionId, + dataToEdit.accession, + fastaId, + ); if (metadataFile === undefined) { toast.error('Please enter metadata.', { position: 'top-center', autoClose: false }); return; diff --git a/website/src/components/Edit/MetadataForm.tsx b/website/src/components/Edit/MetadataForm.tsx index ebe4b20e8c..967a2c902e 100644 --- a/website/src/components/Edit/MetadataForm.tsx +++ b/website/src/components/Edit/MetadataForm.tsx @@ -4,7 +4,7 @@ import { Fragment, type Dispatch, type FC, type SetStateAction } from 'react'; import { EditableDataRow } from './DataRow.tsx'; import type { Row } from './InputField'; -import { ACCESSION_FIELD, SUBMISSION_ID_INPUT_FIELD } from '../../settings.ts'; +import { ACCESSION_FIELD, FASTA_ID_FIELD, SUBMISSION_ID_INPUT_FIELD } from '../../settings.ts'; import { mapErrorsAndWarnings, type SequenceEntryToEdit } from '../../types/backend.ts'; import type { InputField } from '../../types/config'; @@ -71,8 +71,9 @@ export class EditableMetadata { * @param submissionId optional (might already be in the rows if add to the form initially). * The submission ID to put into the TSV. * @param accession optional. If an accession is already assigned to this sequence, it should be given. + * @param fastaId optional. Add a fastaId field with content if supplied. */ - getMetadataTsv(submissionId?: string, accession?: string): File | undefined { + getMetadataTsv(submissionId?: string, accession?: string, fastaId?: string): File | undefined { // if no values are set at all, return undefined if (!this.rows.some((row) => row.value !== '')) return undefined; @@ -93,6 +94,10 @@ export class EditableMetadata { tsvFields.set(ACCESSION_FIELD, accession); } + if (fastaId) { + tsvFields.set(FASTA_ID_FIELD, fastaId); + } + const tsvContent = Papa.unparse([Array.from(tsvFields.keys()), Array.from(tsvFields.values())], { delimiter: '\t', newline: '\n', diff --git a/website/src/components/Edit/SequencesForm.spec.tsx b/website/src/components/Edit/SequencesForm.spec.tsx index 8b09d7c05f..3428287115 100644 --- a/website/src/components/Edit/SequencesForm.spec.tsx +++ b/website/src/components/Edit/SequencesForm.spec.tsx @@ -62,7 +62,7 @@ describe('SequencesForm', () => { expect(fasta).not.toBeUndefined(); const fastaText = await fasta!.text(); expect.soft(fastaText).toBe('>subId_Segment1\nATCG'); - expect(editableSequences.getSequenceRecord()).deep.equals({ 'Segment 1': 'ATCG' }); + expect(editableSequences.getSequenceRecord()).deep.equals({ subId_Segment1: 'ATCG' }); const rows = editableSequences.rows; expect(rows).toEqual([ @@ -84,7 +84,7 @@ describe('SequencesForm', () => { expect(fasta).not.toBeUndefined(); const fastaText = await fasta!.text(); expect.soft(fastaText).toBe('>subId_Segment1\nATCG'); - expect(editableSequences.getSequenceRecord()).deep.equals({ 'Segment 1': 'ATCG' }); + expect(editableSequences.getSequenceRecord()).deep.equals({ subId_Segment1: 'ATCG' }); const rows = editableSequences.rows; expect(rows).toEqual([ @@ -117,7 +117,7 @@ describe('SequencesForm', () => { expect(fasta).not.toBeUndefined(); const fastaText = await fasta!.text(); expect.soft(fastaText).toBe('>subId_Segment1\nATCG'); - expect(editableSequences.getSequenceRecord()).deep.equals({ 'Segment 1': 'ATCG' }); + expect(editableSequences.getSequenceRecord()).deep.equals({ subId_Segment1: 'ATCG' }); const rows = editableSequences.rows; expect(rows).toEqual([ @@ -133,7 +133,7 @@ describe('SequencesForm', () => { expect(fasta).not.toBeUndefined(); const fastaText = await fasta!.text(); expect.soft(fastaText).toBe('>subId_Segment1\nATCG\n>subId_Segment2\nTT'); - expect(editableSequences.getSequenceRecord()).deep.equals({ 'Segment 1': 'ATCG', 'Segment 2': 'TT' }); + expect(editableSequences.getSequenceRecord()).deep.equals({ subId_Segment1: 'ATCG', subId_Segment2: 'TT' }); const rows = editableSequences.rows; expect(rows).deep.equals([ @@ -189,7 +189,7 @@ describe('SequencesForm', () => { const fastaText = await fasta!.text(); expect.soft(fastaText).toBe('>subId\nATCG'); - expect(editableSequences.getSequenceRecord()).deep.equals({ [key]: 'ATCG' }); + expect(editableSequences.getSequenceRecord()).deep.equals({ subId: 'ATCG' }); const rows = editableSequences.rows; expect(rows).deep.equals([{ label: key, value: 'ATCG', initialValue: null, fastaHeader: 'subId', key }]); @@ -229,7 +229,7 @@ describe('SequencesForm', () => { const fastaText = await fasta!.text(); expect.soft(fastaText).toBe('>subId_label\nATCG'); - expect(editableSequences.getSequenceRecord()).deep.equals({ label: 'ATCG' }); + expect(editableSequences.getSequenceRecord()).deep.equals({ subId_label: 'ATCG' }); }); test('GIVEN initial segment data that is then deleted as an edit THEN the edit record does not contain the segment key but input field is kept', () => { @@ -240,8 +240,8 @@ describe('SequencesForm', () => { expect(editableSequences.rows).toEqual([ { - label: 'originalSequenceName', - fastaHeader: 'defaultSubmitter_originalSequenceName', + label: 'originalFastaHeader (mapped to unalignedProcessedSequenceName)', + fastaHeader: 'originalFastaHeader', value: 'originalUnalignedNucleotideSequencesValue', initialValue: 'originalUnalignedNucleotideSequencesValue', key: expect.any(String), diff --git a/website/src/components/Edit/SequencesForm.tsx b/website/src/components/Edit/SequencesForm.tsx index 1f9ac22653..b404e963f5 100644 --- a/website/src/components/Edit/SequencesForm.tsx +++ b/website/src/components/Edit/SequencesForm.tsx @@ -61,6 +61,18 @@ export class EditableSequences { return this.maxNumberOfRows > 1; } + static invertRecordMulti(obj: Record): Record { + const inverted: Record = {}; + + for (const key in obj) { + const value = obj[key]; + if (value === null) continue; + (inverted[value] ??= []).push(key); + } + + return inverted; + } + /** * @param initialData The sequence entry to edit, from which the initial sequence data is taken. * @param referenceGenomeLightweightSchema @@ -70,19 +82,27 @@ export class EditableSequences { referenceGenomeLightweightSchema: ReferenceGenomesLightweightSchema, ): EditableSequences { const maxNumberRows = this.getMaxNumberOfRows(referenceGenomeLightweightSchema); + const fastaHeaderMap = EditableSequences.invertRecordMulti( + initialData.processedData.sequenceNameToFastaHeaderMap, + ); const existingDataRows = Object.entries(initialData.originalData.unalignedNucleotideSequences).map( - ([key, value]) => ({ - // TODO: for now key corresponds to the segment name in future it will be the fastaHeader - label: key, // TODO: In future prepro will map the fastaHeader to the segment (will be added to the label) - fastaHeader: - maxNumberRows > 1 - ? `${initialData.submissionId}_${key.replace(/\s+/g, '')}` - : initialData.submissionId, // TODO: in future will come from the key - value: value, - initialValue: value, - key: EditableSequences.getNextKey(), - }), + ([key, value]) => { + const mapped = (fastaHeaderMap[key] ?? []).join(', ') || ''; + const label = !mapped + ? `${key} (could not be classified)` + : mapped === key + ? key + : `${key} (mapped to ${mapped})`; + return { + label, + fastaHeader: maxNumberRows > 1 ? key : initialData.submissionId, + value: value, + initialValue: value, + key: EditableSequences.getNextKey(), + }; + }, ); + return new EditableSequences(existingDataRows, maxNumberRows); } @@ -139,6 +159,11 @@ export class EditableSequences { ); } + getFastaIds(): string { + const filledRows = this.rows.filter((row) => row.value !== null); + return filledRows.map((sequence) => sequence.fastaHeader).join(','); + } + getSequenceFasta(): File | undefined { const filledRows = this.rows.filter((row) => row.value !== null); @@ -163,7 +188,7 @@ export class EditableSequences { ); return filledRows.reduce>((prev, row) => { - prev[row.label] = row.value; //TODO: this will have to be changed to fastaHeader in future + prev[row.fastaHeader] = row.value; return prev; }, {}); } diff --git a/website/src/components/ReviewPage/SequencesDialog.spec.tsx b/website/src/components/ReviewPage/SequencesDialog.spec.tsx index 5f106af900..98abed902a 100644 --- a/website/src/components/ReviewPage/SequencesDialog.spec.tsx +++ b/website/src/components/ReviewPage/SequencesDialog.spec.tsx @@ -93,6 +93,10 @@ const dataToView: SequenceEntryToEdit = { }, nucleotideInsertions: {}, aminoAcidInsertions: {}, + sequenceNameToFastaHeaderMap: { + [sequence1]: 'header1', + [sequence2]: 'header2', + }, files: null, }, status: processedStatus, diff --git a/website/src/components/Submission/FileUpload/SequenceEntryUploadComponent.tsx b/website/src/components/Submission/FileUpload/SequenceEntryUploadComponent.tsx index 4d560a85ab..c2e94f12bf 100644 --- a/website/src/components/Submission/FileUpload/SequenceEntryUploadComponent.tsx +++ b/website/src/components/Submission/FileUpload/SequenceEntryUploadComponent.tsx @@ -9,7 +9,6 @@ import { ColumnMappingModal } from './ColumnMappingModal'; import { FileUploadComponent } from './FileUploadComponent'; import { FASTA_FILE_KIND, METADATA_FILE_KIND, RawFile, type ProcessedFile } from './fileProcessing'; import type { InputField } from '../../../types/config'; -import { getFirstLightweightSchema, type ReferenceGenomesLightweightSchema } from '../../../types/referencesGenomes'; import { dataUploadDocsUrl } from '../dataUploadDocsUrl'; type SequenceEntryUploadProps = { @@ -21,7 +20,6 @@ type SequenceEntryUploadProps = { setSequenceFile: Dispatch>; columnMapping: ColumnMapping | null; setColumnMapping: Dispatch>; - referenceGenomeLightweightSchema: ReferenceGenomesLightweightSchema; metadataTemplateFields: Map; enableConsensusSequences: boolean; isMultiSegmented: boolean; @@ -39,7 +37,6 @@ export const SequenceEntryUpload: FC = ({ setSequenceFile, columnMapping, setColumnMapping, - referenceGenomeLightweightSchema, metadataTemplateFields, enableConsensusSequences, isMultiSegmented, @@ -98,23 +95,9 @@ export const SequenceEntryUpload: FC = ({ {isMultiSegmented && (

{organism.toUpperCase()} has a multi-segmented genome. Please submit one metadata entry with a - unique submissionId for the full multi-segmented sample, e.g. sample1. Sequence - data should be a FASTA file with each header indicating the submissionId and the segment, - i.e.{' '} - {getFirstLightweightSchema(referenceGenomeLightweightSchema).nucleotideSegmentNames.map( - (name, index) => ( - - sample1_{name} - {index !== - getFirstLightweightSchema(referenceGenomeLightweightSchema).nucleotideSegmentNames - .length - - 1 - ? ', ' - : ''} - - ), - )} - . + unique submissionId column for the full multi-segmented sample, e.g. sample1 and a{' '} + fastaId column with a space-separated list of the fasta headers of all segments, e.g.{' '} + fastaHeaderSegment1 fastaHeaderSegment2 fastaHeaderSegment3.

)} diff --git a/website/src/components/Submission/FormOrUploadWrapper.tsx b/website/src/components/Submission/FormOrUploadWrapper.tsx index 01a322c52d..5c044f60e4 100644 --- a/website/src/components/Submission/FormOrUploadWrapper.tsx +++ b/website/src/components/Submission/FormOrUploadWrapper.tsx @@ -85,7 +85,8 @@ export const FormOrUploadWrapper: FC = ({ if (!submissionId) { return { type: 'error', errorMessage: 'Please specify an ID.' }; } - const metadataFile = editableMetadata.getMetadataTsv(); + const fastaId = enableConsensusSequences ? editableSequences.getFastaIds() : undefined; + const metadataFile = editableMetadata.getMetadataTsv(undefined, undefined, fastaId); if (!metadataFile) { return { type: 'error', errorMessage: 'Please specify metadata.' }; } @@ -137,7 +138,6 @@ export const FormOrUploadWrapper: FC = ({ setSequenceFile={setSequenceFile} columnMapping={columnMapping} setColumnMapping={setColumnMapping} - referenceGenomeLightweightSchema={referenceGenomeLightweightSchema} metadataTemplateFields={metadataTemplateFields} enableConsensusSequences={enableConsensusSequences} isMultiSegmented={isMultiSegmented} diff --git a/website/src/settings.ts b/website/src/settings.ts index ae02cc05ca..829b7664c7 100644 --- a/website/src/settings.ts +++ b/website/src/settings.ts @@ -2,6 +2,7 @@ export const pageSize = 100; export const ACCESSION_VERSION_FIELD = 'accessionVersion'; export const ACCESSION_FIELD = 'accession'; +export const FASTA_ID_FIELD = 'fastaId'; export const VERSION_FIELD = 'version'; export const VERSION_STATUS_FIELD = 'versionStatus'; export const IS_REVOCATION_FIELD = 'isRevocation'; diff --git a/website/src/types/backend.ts b/website/src/types/backend.ts index 20b69f7c89..21310175e6 100644 --- a/website/src/types/backend.ts +++ b/website/src/types/backend.ts @@ -234,6 +234,7 @@ export const sequenceEntryToEdit = accessionVersion.merge( nucleotideInsertions: z.record(z.array(z.string())), alignedAminoAcidSequences: z.record(z.string().nullable()), aminoAcidInsertions: z.record(z.array(z.string())), + sequenceNameToFastaHeaderMap: z.record(z.string().nullable()), files: filesByCategory.nullable(), }), }), diff --git a/website/vitest.setup.ts b/website/vitest.setup.ts index fd8c90923d..ff28ccff4b 100755 --- a/website/vitest.setup.ts +++ b/website/vitest.setup.ts @@ -95,7 +95,7 @@ export const defaultReviewData: SequenceEntryToEdit = { [metadataKey]: editableEntry, }, unalignedNucleotideSequences: { - originalSequenceName: 'originalUnalignedNucleotideSequencesValue', + originalFastaHeader: 'originalUnalignedNucleotideSequencesValue', }, files: null, }, @@ -119,6 +119,9 @@ export const defaultReviewData: SequenceEntryToEdit = { aminoAcidInsertions: { processedInsertionGeneName: ['aminoAcidInsertion1', 'aminoAcidInsertion2'], }, + sequenceNameToFastaHeaderMap: { + unalignedProcessedSequenceName: 'originalFastaHeader', + }, files: null, }, submissionId: 'defaultSubmitter', From 8d566bf0087ebcc31b3a300b14f557b381adb562 Mon Sep 17 00:00:00 2001 From: anna-parker <50943381+anna-parker@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:00:18 +0100 Subject: [PATCH 11/44] fix backend --- backend/docs/db/schema.sql | 9 +- .../SubmissionControllerDescriptions.kt | 5 +- .../submission/UploadDatabaseService.kt | 2 +- .../dbtables/MetadataUploadAuxTable.kt | 2 +- .../migration/V1.22__refactor_aux_tables.sql | 18 +- .../submission/SubmitEndpointTest.kt | 225 +++++++++--------- .../specs/features/revise-sequence.spec.ts | 4 +- .../FileUpload/fileProcessing.spec.ts | 2 + 8 files changed, 137 insertions(+), 130 deletions(-) diff --git a/backend/docs/db/schema.sql b/backend/docs/db/schema.sql index ec22ccc37c..62d0ef8e6f 100644 --- a/backend/docs/db/schema.sql +++ b/backend/docs/db/schema.sql @@ -379,7 +379,7 @@ CREATE TABLE public.metadata_upload_aux_table ( uploaded_at timestamp without time zone NOT NULL, metadata jsonb NOT NULL, files jsonb, - fasta_ids jsonb DEFAULT '[]'::jsonb + fasta_ids text[] ); @@ -794,13 +794,6 @@ CREATE INDEX data_use_terms_table_accession_idx ON public.data_use_terms_table U CREATE INDEX flyway_schema_history_s_idx ON public.flyway_schema_history USING btree (success); --- --- Name: metadata_upload_aux_table_fasta_ids_idx; Type: INDEX; Schema: public; Owner: postgres --- - -CREATE INDEX metadata_upload_aux_table_fasta_ids_idx ON public.metadata_upload_aux_table USING gin (fasta_ids jsonb_path_ops); - - -- -- Name: sequence_entries_organism_idx; Type: INDEX; Schema: public; Owner: postgres -- diff --git a/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionControllerDescriptions.kt b/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionControllerDescriptions.kt index 44070c5785..d33f65d399 100644 --- a/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionControllerDescriptions.kt +++ b/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionControllerDescriptions.kt @@ -1,5 +1,6 @@ package org.loculus.backend.controller +import org.loculus.backend.model.FASTA_ID_HEADER import org.loculus.backend.model.METADATA_ID_HEADER const val SUBMIT_RESPONSE_DESCRIPTION = """ @@ -22,14 +23,14 @@ The field '$METADATA_ID_HEADER' is required and must be unique within the provid It is used to associate metadata to the sequences in the sequences fasta file. """ -// TODO: update description const val SEQUENCE_FILE_DESCRIPTION = """ A fasta file containing the unaligned nucleotide sequences of the submitted sequences. The file may be compressed with zstd, xz, zip, gzip, lzma, bzip2 (with common extensions). If the underlying organism has a single segment, the headers of the fasta file must match the '$METADATA_ID_HEADER' field in the metadata file. If the underlying organism has multiple segments, -the headers of the fasta file must be of the form '>[$METADATA_ID_HEADER]_[segmentName]'. +the headers of the fasta file must be added in a space-separated list to the '$FASTA_ID_HEADER' +field in the metadata file. """ const val FILE_MAPPING_DESCRIPTION = """ diff --git a/backend/src/main/kotlin/org/loculus/backend/service/submission/UploadDatabaseService.kt b/backend/src/main/kotlin/org/loculus/backend/service/submission/UploadDatabaseService.kt index fc2293666b..a568d412f7 100644 --- a/backend/src/main/kotlin/org/loculus/backend/service/submission/UploadDatabaseService.kt +++ b/backend/src/main/kotlin/org/loculus/backend/service/submission/UploadDatabaseService.kt @@ -229,7 +229,7 @@ class UploadDatabaseService( SELECT jsonb_object_agg(s.fasta_id, s.compressed_sequence_data::jsonb) AS seq_map FROM sequence_upload_aux_table AS s WHERE s.upload_id = m.upload_id - AND jsonb_exists(COALESCE(m.fasta_ids, '[]'::jsonb), s.fasta_id) + AND s.fasta_id = ANY (COALESCE(m.fasta_ids, ARRAY[]::text[])) ) AS x ON TRUE WHERE m.upload_id = ? RETURNING accession, version, submission_id; diff --git a/backend/src/main/kotlin/org/loculus/backend/service/submission/dbtables/MetadataUploadAuxTable.kt b/backend/src/main/kotlin/org/loculus/backend/service/submission/dbtables/MetadataUploadAuxTable.kt index b4a234b037..3f2de716c7 100644 --- a/backend/src/main/kotlin/org/loculus/backend/service/submission/dbtables/MetadataUploadAuxTable.kt +++ b/backend/src/main/kotlin/org/loculus/backend/service/submission/dbtables/MetadataUploadAuxTable.kt @@ -13,7 +13,7 @@ object MetadataUploadAuxTable : Table(METADATA_UPLOAD_AUX_TABLE_NAME) { val uploadIdColumn = varchar("upload_id", 255) val organismColumn = varchar("organism", 255) val submissionIdColumn = varchar("submission_id", 255) - val fastaIdsColumn = jacksonSerializableJsonb>("fasta_ids").nullable() + val fastaIdsColumn = array("fasta_ids").nullable() val submitterColumn = varchar("submitter", 255) val groupIdColumn = integer("group_id").nullable() val uploadedAtColumn = datetime("uploaded_at") diff --git a/backend/src/main/resources/db/migration/V1.22__refactor_aux_tables.sql b/backend/src/main/resources/db/migration/V1.22__refactor_aux_tables.sql index 4e30ddc7fd..79f8fab904 100644 --- a/backend/src/main/resources/db/migration/V1.22__refactor_aux_tables.sql +++ b/backend/src/main/resources/db/migration/V1.22__refactor_aux_tables.sql @@ -1,12 +1,20 @@ +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM sequence_upload_aux_table + ) THEN + RAISE EXCEPTION + 'Cannot alter sequence_upload_aux_table: table is not empty. Wait for submissions to finish processing before running this migration.'; + END IF; +END $$; + ALTER TABLE metadata_upload_aux_table - ADD COLUMN fasta_ids jsonb DEFAULT '[]'::jsonb; + ADD COLUMN fasta_ids text[]; ALTER TABLE sequence_upload_aux_table DROP CONSTRAINT sequence_upload_aux_table_pkey, DROP COLUMN submission_id, ADD COLUMN fasta_id text NOT NULL, DROP COLUMN segment_name, - ADD CONSTRAINT sequence_upload_aux_table_pkey PRIMARY KEY (upload_id, fasta_id); - -CREATE INDEX ON metadata_upload_aux_table - USING GIN (fasta_ids jsonb_path_ops); \ No newline at end of file + ADD CONSTRAINT sequence_upload_aux_table_pkey PRIMARY KEY (upload_id, fasta_id); \ No newline at end of file diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointTest.kt index 8af3ea51ea..7214574a0a 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointTest.kt @@ -157,118 +157,6 @@ class SubmitEndpointTest( .andExpect(jsonPath("\$[0].version").value(1)) } - @Test - fun `GIVEN valid input data THEN data is accepted and shows fastaID`() { - val groupId = groupManagementClient.createNewGroup().andGetGroupId() - - submissionControllerClient.submit( - SubmitFiles.metadataFileWith( - content = """ - submissionId firstColumn - header1 someValue - header2 someValue - """.trimIndent(), - ), - SubmitFiles.sequenceFileWith( - content = """ - >header1 - AC - >header2 - GT - """.trimIndent(), - ), - groupId = groupId, - ) - .andExpect(status().isOk) - .andExpect(content().contentType(APPLICATION_JSON_VALUE)) - .andExpect(jsonPath("\$.length()").value(2)) - .andExpect(jsonPath("\$[0].submissionId").value("header1")) - .andExpect(jsonPath("\$[0].accession", containsString(backendConfig.accessionPrefix))) - .andExpect(jsonPath("\$[0].version").value(1)) - - val unalignedNucleotideSequences = convenienceClient.extractUnprocessedData()[0] - .data - .unalignedNucleotideSequences - - assertThat(unalignedNucleotideSequences, hasEntry("header1", "AC")) - } - - @Test - fun `GIVEN valid input multi segment data THEN data is accepted and originalData shows fastaID`() { - val groupId = groupManagementClient.createNewGroup().andGetGroupId() - - submissionControllerClient.submit( - SubmitFiles.metadataFileWith( - content = """ - submissionId firstColumn fastaId - header1 someValue header1_seg1 header1_seg2 - header2 someValue fasta_header2_seg1 - """.trimIndent(), - ), - SubmitFiles.sequenceFileWith( - content = """ - >header1_seg1 - AC - >header1_seg2 - GT - >fasta_header2_seg1 - TA - """.trimIndent(), - ), - organism = OTHER_ORGANISM, - groupId = groupId, - ) - .andExpect(status().isOk) - .andExpect(content().contentType(APPLICATION_JSON_VALUE)) - .andExpect(jsonPath("\$.length()").value(2)) - .andExpect(jsonPath("\$[0].submissionId").value("header1")) - .andExpect(jsonPath("\$[0].accession", containsString(backendConfig.accessionPrefix))) - .andExpect(jsonPath("\$[0].version").value(1)) - - val unalignedNucleotideSequences = convenienceClient.extractUnprocessedData(organism = OTHER_ORGANISM)[0] - .data - .unalignedNucleotideSequences - - assertThat(unalignedNucleotideSequences, hasEntry("header1_seg1", "AC")) - } - - @Test - fun `GIVEN valid input multi segment data without fastaID THEN data is accepted and originalData shows fastaID`() { - val groupId = groupManagementClient.createNewGroup().andGetGroupId() - - submissionControllerClient.submit( - SubmitFiles.metadataFileWith( - content = """ - submissionId firstColumn - header1 someValue - header2 someValue - """.trimIndent(), - ), - SubmitFiles.sequenceFileWith( - content = """ - >header1 - AC - >header2 - TA - """.trimIndent(), - ), - organism = OTHER_ORGANISM, - groupId = groupId, - ) - .andExpect(status().isOk) - .andExpect(content().contentType(APPLICATION_JSON_VALUE)) - .andExpect(jsonPath("\$.length()").value(2)) - .andExpect(jsonPath("\$[0].submissionId").value("header1")) - .andExpect(jsonPath("\$[0].accession", containsString(backendConfig.accessionPrefix))) - .andExpect(jsonPath("\$[0].version").value(1)) - - val unalignedNucleotideSequences = convenienceClient.extractUnprocessedData(organism = OTHER_ORGANISM)[0] - .data - .unalignedNucleotideSequences - - assertThat(unalignedNucleotideSequences, hasEntry("header1", "AC")) - } - @Test fun `GIVEN submission without data use terms THEN returns an error`() { submissionControllerClient.submitWithoutDataUseTerms( @@ -280,6 +168,7 @@ class SubmitEndpointTest( .andExpect(status().isBadRequest) } + @Test fun `GIVEN submission with file mapping THEN returns an error`() { submissionControllerClient.submit( @@ -659,4 +548,116 @@ class SubmitEndpointTest( ), ) } + + @Test + fun `GIVEN valid input data THEN data is accepted and shows fastaID`() { + val groupId = groupManagementClient.createNewGroup().andGetGroupId() + + submissionControllerClient.submit( + SubmitFiles.metadataFileWith( + content = """ + submissionId firstColumn + header1 someValue + header2 someValue + """.trimIndent(), + ), + SubmitFiles.sequenceFileWith( + content = """ + >header1 + AC + >header2 + GT + """.trimIndent(), + ), + groupId = groupId, + ) + .andExpect(status().isOk) + .andExpect(content().contentType(APPLICATION_JSON_VALUE)) + .andExpect(jsonPath("\$.length()").value(2)) + .andExpect(jsonPath("\$[0].submissionId").value("header1")) + .andExpect(jsonPath("\$[0].accession", containsString(backendConfig.accessionPrefix))) + .andExpect(jsonPath("\$[0].version").value(1)) + + val unalignedNucleotideSequences = convenienceClient.extractUnprocessedData()[0] + .data + .unalignedNucleotideSequences + + assertThat(unalignedNucleotideSequences, hasEntry("header1", "AC")) + } + + @Test + fun `GIVEN valid input multi segment data THEN data is accepted and originalData shows fastaID`() { + val groupId = groupManagementClient.createNewGroup().andGetGroupId() + + submissionControllerClient.submit( + SubmitFiles.metadataFileWith( + content = """ + submissionId firstColumn fastaId + header1 someValue header1_seg1 header1_seg2 + header2 someValue fasta_header2_seg1 + """.trimIndent(), + ), + SubmitFiles.sequenceFileWith( + content = """ + >header1_seg1 + AC + >header1_seg2 + GT + >fasta_header2_seg1 + TA + """.trimIndent(), + ), + organism = OTHER_ORGANISM, + groupId = groupId, + ) + .andExpect(status().isOk) + .andExpect(content().contentType(APPLICATION_JSON_VALUE)) + .andExpect(jsonPath("\$.length()").value(2)) + .andExpect(jsonPath("\$[0].submissionId").value("header1")) + .andExpect(jsonPath("\$[0].accession", containsString(backendConfig.accessionPrefix))) + .andExpect(jsonPath("\$[0].version").value(1)) + + val unalignedNucleotideSequences = convenienceClient.extractUnprocessedData(organism = OTHER_ORGANISM)[0] + .data + .unalignedNucleotideSequences + + assertThat(unalignedNucleotideSequences, hasEntry("header1_seg1", "AC")) + } + + @Test + fun `GIVEN valid input multi segment data without fastaID THEN data is accepted and originalData shows fastaID`() { + val groupId = groupManagementClient.createNewGroup().andGetGroupId() + + submissionControllerClient.submit( + SubmitFiles.metadataFileWith( + content = """ + submissionId firstColumn + header1 someValue + header2 someValue + """.trimIndent(), + ), + SubmitFiles.sequenceFileWith( + content = """ + >header1 + AC + >header2 + TA + """.trimIndent(), + ), + organism = OTHER_ORGANISM, + groupId = groupId, + ) + .andExpect(status().isOk) + .andExpect(content().contentType(APPLICATION_JSON_VALUE)) + .andExpect(jsonPath("\$.length()").value(2)) + .andExpect(jsonPath("\$[0].submissionId").value("header1")) + .andExpect(jsonPath("\$[0].accession", containsString(backendConfig.accessionPrefix))) + .andExpect(jsonPath("\$[0].version").value(1)) + + val unalignedNucleotideSequences = convenienceClient.extractUnprocessedData(organism = OTHER_ORGANISM)[0] + .data + .unalignedNucleotideSequences + + assertThat(unalignedNucleotideSequences, hasEntry("header1", "AC")) + } } diff --git a/integration-tests/tests/specs/features/revise-sequence.spec.ts b/integration-tests/tests/specs/features/revise-sequence.spec.ts index bd1cf1fce0..cc8e2db1ff 100644 --- a/integration-tests/tests/specs/features/revise-sequence.spec.ts +++ b/integration-tests/tests/specs/features/revise-sequence.spec.ts @@ -62,7 +62,9 @@ sequenceTest( expect(tabs).toContain('S (unaligned)'); await reviewPage.switchSequenceTab('S (unaligned)'); - expect(await reviewPage.getSequenceContent()).toBe(newSsequence); + const actual = (await reviewPage.getSequenceContent()).replace(/\s+/g, ''); + const expected = newSsequence.replace(/\s+/g, ''); + expect(actual.startsWith(expected)).toBe(true); await reviewPage.closeSequencesDialog(); }, diff --git a/website/src/components/Submission/FileUpload/fileProcessing.spec.ts b/website/src/components/Submission/FileUpload/fileProcessing.spec.ts index d1d05acd1d..f3fc7af470 100644 --- a/website/src/components/Submission/FileUpload/fileProcessing.spec.ts +++ b/website/src/components/Submission/FileUpload/fileProcessing.spec.ts @@ -78,5 +78,7 @@ describe('fileProcessing', () => { const processedFile = result._unsafeUnwrap(); const processedText = await processedFile.text(); expect(processedText).toBe('ACTGACTGACTG'); + const processedHeader = await processedFile.header(); + expect(processedHeader).toBe('fooid'); }); }); From 451b944ce70fd0356d979abd312aa5b38728d95a Mon Sep 17 00:00:00 2001 From: Fabian Engelniederhammer <92720311+fengelniederhammer@users.noreply.github.com> Date: Fri, 21 Nov 2025 07:47:57 +0100 Subject: [PATCH 12/44] create a separate interface for files with a fasta header (#5495) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit targetting https://github.com/loculus-project/loculus/pull/5382#discussion_r2511216041 🚀 Preview: Add `preview` label to enable --- website/src/components/Edit/SequencesForm.tsx | 6 ++-- .../FileUpload/FileUploadComponent.tsx | 12 +++---- .../FileUpload/fileProcessing.spec.ts | 3 +- .../Submission/FileUpload/fileProcessing.ts | 36 ++++++++++--------- 4 files changed, 30 insertions(+), 27 deletions(-) diff --git a/website/src/components/Edit/SequencesForm.tsx b/website/src/components/Edit/SequencesForm.tsx index b404e963f5..beb7813bed 100644 --- a/website/src/components/Edit/SequencesForm.tsx +++ b/website/src/components/Edit/SequencesForm.tsx @@ -4,7 +4,7 @@ import { toast } from 'react-toastify'; import { type SequenceEntryToEdit } from '../../types/backend.ts'; import type { ReferenceGenomesLightweightSchema } from '../../types/referencesGenomes.ts'; import { FileUploadComponent } from '../Submission/FileUpload/FileUploadComponent.tsx'; -import { PLAIN_SEGMENT_KIND, VirtualFile } from '../Submission/FileUpload/fileProcessing.ts'; +import { PLAIN_SEGMENT_KIND, VirtualPlainSegmentFile } from '../Submission/FileUpload/fileProcessing.ts'; function generateAndDownloadFastaFile(fastaHeader: string, sequenceData: string) { const trimmedHeader = fastaHeader.replace(/\s+/g, ''); @@ -219,7 +219,7 @@ export const SequencesForm: FC = ({ { const text = file ? await file.text() : null; - const fastaHeader = file ? await file.header() : null; + const fastaHeader = file?.fastaHeader() ?? null; setEditableSequences((editableSequences) => editableSequences.update(field.key, text, fastaHeader, fastaHeader), ); @@ -230,7 +230,7 @@ export const SequencesForm: FC = ({ small={true} initialValue={ field.initialValue !== null - ? new VirtualFile(field.initialValue, 'Existing data') + ? new VirtualPlainSegmentFile(field.initialValue, 'Existing data') : undefined } showUndo={field.initialValue !== null} diff --git a/website/src/components/Submission/FileUpload/FileUploadComponent.tsx b/website/src/components/Submission/FileUpload/FileUploadComponent.tsx index 9ff669007d..5279f9c522 100644 --- a/website/src/components/Submission/FileUpload/FileUploadComponent.tsx +++ b/website/src/components/Submission/FileUpload/FileUploadComponent.tsx @@ -7,7 +7,7 @@ import { Button } from '../../common/Button'; import IcBaselineDownload from '~icons/ic/baseline-download'; import UndoTwoToneIcon from '~icons/ic/twotone-undo'; -export const FileUploadComponent = ({ +export const FileUploadComponent = ({ setFile, name, ariaLabel, @@ -18,17 +18,17 @@ export const FileUploadComponent = ({ onDownload = undefined, downloadDisabled = false, }: { - setFile: (file: ProcessedFile | undefined) => Promise | void; + setFile: (file: F | undefined) => Promise | void; name: string; ariaLabel: string; - fileKind: FileKind; + fileKind: FileKind; small?: boolean; - initialValue?: ProcessedFile; + initialValue?: F; showUndo?: boolean; onDownload?: () => void; downloadDisabled?: boolean; }) => { - const [myFile, rawSetMyFile] = useState(initialValue); + const [myFile, rawSetMyFile] = useState(initialValue); const [isDragOver, setIsDragOver] = useState(false); const isClient = useClientFlag(); @@ -36,7 +36,7 @@ export const FileUploadComponent = ({ const setMyFile = useCallback( async (file: File | null) => { - let processedFile: ProcessedFile | undefined = undefined; + let processedFile: F | undefined = undefined; if (file !== null) { const processingResult = await fileKind.processRawFile(file); processedFile = processingResult.match( diff --git a/website/src/components/Submission/FileUpload/fileProcessing.spec.ts b/website/src/components/Submission/FileUpload/fileProcessing.spec.ts index f3fc7af470..96a5e0223a 100644 --- a/website/src/components/Submission/FileUpload/fileProcessing.spec.ts +++ b/website/src/components/Submission/FileUpload/fileProcessing.spec.ts @@ -78,7 +78,6 @@ describe('fileProcessing', () => { const processedFile = result._unsafeUnwrap(); const processedText = await processedFile.text(); expect(processedText).toBe('ACTGACTGACTG'); - const processedHeader = await processedFile.header(); - expect(processedHeader).toBe('fooid'); + expect(processedFile.fastaHeader()).toBe('fooid'); }); }); diff --git a/website/src/components/Submission/FileUpload/fileProcessing.ts b/website/src/components/Submission/FileUpload/fileProcessing.ts index b79087e1d9..b28ee15c48 100644 --- a/website/src/components/Submission/FileUpload/fileProcessing.ts +++ b/website/src/components/Submission/FileUpload/fileProcessing.ts @@ -10,16 +10,16 @@ import PhDnaLight from '~icons/ph/dna-light'; type Icon = ForwardRefExoticComponent>; -export type FileKind = { +export type FileKind = { type: 'metadata' | 'fasta' | 'singleSegment'; icon: Icon; supportedExtensions: string[]; - processRawFile: (file: File) => Promise>; + processRawFile: (file: File) => Promise>; }; const COMPRESSION_EXTENSIONS = ['zst', 'gz', 'zip', 'xz']; -export const METADATA_FILE_KIND: FileKind = { +export const METADATA_FILE_KIND: FileKind = { type: 'metadata', icon: MaterialSymbolsLightDataTableOutline, supportedExtensions: ['tsv', 'xlsx', 'xls'], @@ -59,7 +59,7 @@ export const METADATA_FILE_KIND: FileKind = { }, }; -export const FASTA_FILE_KIND: FileKind = { +export const FASTA_FILE_KIND: FileKind = { type: 'fasta', icon: PhDnaLight, supportedExtensions: ['fasta'], @@ -72,7 +72,7 @@ export const FASTA_FILE_KIND: FileKind = { * Can be multiple lines, the lines will be concatenated, and whitespace stripped on both ends. * Compression not supported. */ -export const PLAIN_SEGMENT_KIND: FileKind = { +export const PLAIN_SEGMENT_KIND: FileKind = { type: 'singleSegment', icon: PhDnaLight, supportedExtensions: ['sequence'], @@ -110,7 +110,7 @@ export const PLAIN_SEGMENT_KIND: FileKind = { text: () => Promise.resolve(segmentData), handle: () => file, warnings: () => [], - header: () => Promise.resolve(header), + fastaHeader: () => header, }); }, }; @@ -121,8 +121,6 @@ export interface ProcessedFile { text(): Promise; - header(): Promise; - /* The handle to the file on disk. */ handle(): File; @@ -130,6 +128,10 @@ export interface ProcessedFile { warnings(): string[]; } +export interface ProcessedPlainSegmentFile extends ProcessedFile { + fastaHeader(): string | null; +} + export const dummy = 0; export class RawFile implements ProcessedFile { @@ -147,10 +149,6 @@ export class RawFile implements ProcessedFile { return this.innerFile.text(); } - header(): Promise { - return Promise.resolve(null); - } - warnings(): string[] { return []; } @@ -163,6 +161,16 @@ export class VirtualFile extends RawFile { } } +export class VirtualPlainSegmentFile extends VirtualFile implements ProcessedPlainSegmentFile { + constructor(content: string, fileName: string = 'virtual.txt') { + super(content, fileName); + } + + fastaHeader(): string | null { + return null; + } +} + type SupportedInBrowserCompressionKind = 'zst' | 'gz' | 'zip'; const isSupportedInBrowserCompressionKind = (s: string): s is SupportedInBrowserCompressionKind => ['zst', 'gz', 'zip'].includes(s); @@ -275,10 +283,6 @@ export class ExcelFile implements ProcessedFile { return this.originalFile; } - header(): Promise { - return Promise.resolve(null); - } - warnings(): string[] { return this.processingWarnings; } From 7ac390f6898e32fd99db4d9b86a91de5acf6f4a0 Mon Sep 17 00:00:00 2001 From: anna-parker <50943381+anna-parker@users.noreply.github.com> Date: Fri, 21 Nov 2025 09:03:18 +0100 Subject: [PATCH 13/44] silly formatting error --- .../org/loculus/backend/model/SubmitModel.kt | 7 +- .../loculus/backend/utils/MetadataEntry.kt | 68 ++++++++----------- .../submission/SubmitEndpointTest.kt | 1 - .../backend/utils/MetadataEntryTest.kt | 12 ++-- .../test/resources/metadata_multi_segment.tsv | 6 +- .../revised_metadata_multi_segment.tsv | 6 +- 6 files changed, 44 insertions(+), 56 deletions(-) diff --git a/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt b/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt index c437c38dd2..a2970af41d 100644 --- a/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt +++ b/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt @@ -176,7 +176,7 @@ class SubmitModel( ) val addFastaId = requiresConsensusSequenceFile(submissionParams.organism) try { - uploadMetadata(uploadId, submissionParams, metadataStream, batchSize, addFastaId = addFastaId) + uploadMetadata(uploadId, submissionParams, metadataStream, batchSize) } finally { metadataTempFileToDelete.delete() } @@ -252,7 +252,6 @@ class SubmitModel( submissionParams: SubmissionParams, metadataStream: InputStream, batchSize: Int, - addFastaId: Boolean, ) { log.debug { "intermediate storing uploaded metadata of type ${submissionParams.uploadType.name} " + @@ -262,7 +261,7 @@ class SubmitModel( try { when (submissionParams) { is SubmissionParams.OriginalSubmissionParams -> { - metadataEntryStreamAsSequence(metadataStream, addFastaId) + metadataEntryStreamAsSequence(metadataStream) .chunked(batchSize) .forEach { batch -> uploadDatabaseService.batchInsertMetadataInAuxTable( @@ -278,7 +277,7 @@ class SubmitModel( } is SubmissionParams.RevisionSubmissionParams -> { - revisionEntryStreamAsSequence(metadataStream, addFastaId) + revisionEntryStreamAsSequence(metadataStream) .chunked(batchSize) .forEach { batch -> uploadDatabaseService.batchInsertRevisedMetadataInAuxTable( diff --git a/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt b/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt index 36a4938596..52d975b530 100644 --- a/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt +++ b/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt @@ -2,6 +2,7 @@ package org.loculus.backend.utils import org.apache.commons.csv.CSVException import org.apache.commons.csv.CSVFormat +import org.apache.commons.csv.CSVRecord import org.loculus.backend.controller.UnprocessableEntityException import org.loculus.backend.model.ACCESSION_HEADER import org.loculus.backend.model.FASTA_ID_HEADER @@ -42,17 +43,26 @@ fun findAndValidateSubmissionIdHeader(headerNames: List): String { return submissionIdHeaders.first() } -fun determineFastaIdColumnName(metadataHeaderNames: List, submissionIdHeader: String): String { - if (!metadataHeaderNames.contains(FASTA_ID_HEADER)) { - return submissionIdHeader +fun extractFastaIdsFromRecord(record: CSVRecord, submissionId: String, recordNumber: Int): List { + val headerNames = record.parser.headerNames + return when (headerNames.contains(FASTA_ID_HEADER)) { + true -> { + val fastaIdValues = record[FASTA_ID_HEADER] + if (fastaIdValues.isNullOrEmpty()) { + throw UnprocessableEntityException( + "In metadata file: record #$recordNumber: column `$FASTA_ID_HEADER` is empty. This is invalid. Full record: $record", + ) + } + + fastaIdValues.split(Regex(" ")) + .map { it.trim() } + .filter { it.isNotEmpty() } + } + false -> listOf(submissionId) } - return FASTA_ID_HEADER } -fun metadataEntryStreamAsSequence( - metadataInputStream: InputStream, - addFastaIds: Boolean = true, -): Sequence { +fun metadataEntryStreamAsSequence(metadataInputStream: InputStream): Sequence { val csvParser = try { CSVFormat.TDF.builder().setHeader().setSkipHeaderRecord(true).get() .parse(InputStreamReader(metadataInputStream)) @@ -62,7 +72,6 @@ fun metadataEntryStreamAsSequence( val headerNames = csvParser.headerNames val submissionIdHeader = findAndValidateSubmissionIdHeader(headerNames) - val fastaIdColumn = determineFastaIdColumnName(headerNames, submissionIdHeader) return sequence { try { @@ -82,20 +91,11 @@ fun metadataEntryStreamAsSequence( ) } - var fastaIds: List? = null - - if (addFastaIds) { - val fastaId = record[fastaIdColumn] - if (fastaId.isNullOrEmpty()) { - throw UnprocessableEntityException( - "In metadata file: record #$recordNumber: column `$fastaIdColumn` is empty. This is invalid. Full record: $record", - ) - } - - fastaIds = fastaId.split(Regex("[ ,]+")) - .map { it.trim() } - .filter { it.isNotEmpty() } - } + val fastaIds = extractFastaIdsFromRecord( + record, + submissionId, + recordNumber, + ) val metadata = record.toMap().filterKeys { it != submissionIdHeader } val entry = MetadataEntry(submissionId, metadata, fastaIds) @@ -126,7 +126,7 @@ data class RevisionEntry( val fastaIds: List? = null, ) -fun revisionEntryStreamAsSequence(metadataInputStream: InputStream, addFastaIds: Boolean): Sequence { +fun revisionEntryStreamAsSequence(metadataInputStream: InputStream): Sequence { val csvParser = try { CSVFormat.TDF.builder().setHeader().setSkipHeaderRecord(true).get() .parse(InputStreamReader(metadataInputStream)) @@ -136,7 +136,6 @@ fun revisionEntryStreamAsSequence(metadataInputStream: InputStream, addFastaIds: val headerNames = csvParser.headerNames val submissionIdHeader = findAndValidateSubmissionIdHeader(headerNames) - val fastaIdHeader = determineFastaIdColumnName(headerNames, submissionIdHeader) if (!headerNames.contains(ACCESSION_HEADER)) { throw UnprocessableEntityException( @@ -164,20 +163,11 @@ fun revisionEntryStreamAsSequence(metadataInputStream: InputStream, addFastaIds: ) } - var fastaIds: List? = null - - if (addFastaIds) { - val fastaId = record[fastaIdHeader] - if (fastaId.isNullOrEmpty()) { - throw UnprocessableEntityException( - "A row in metadata file contains no $fastaIdHeader: $record", - ) - } - - fastaIds = fastaId.split(Regex("[ ,]+")) - .map { it.trim() } - .filter { it.isNotEmpty() } - } + val fastaIds = extractFastaIdsFromRecord( + record, + submissionId, + recordNumber, + ) val metadata = record.toMap().filterKeys { it != submissionIdHeader && it != ACCESSION_HEADER } val entry = RevisionEntry(submissionId, accession, metadata, fastaIds) diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointTest.kt index 7214574a0a..4e0e08a0e1 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointTest.kt @@ -168,7 +168,6 @@ class SubmitEndpointTest( .andExpect(status().isBadRequest) } - @Test fun `GIVEN submission with file mapping THEN returns an error`() { submissionControllerClient.submit( diff --git a/backend/src/test/kotlin/org/loculus/backend/utils/MetadataEntryTest.kt b/backend/src/test/kotlin/org/loculus/backend/utils/MetadataEntryTest.kt index 101b87e86c..908261ff69 100644 --- a/backend/src/test/kotlin/org/loculus/backend/utils/MetadataEntryTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/utils/MetadataEntryTest.kt @@ -133,7 +133,7 @@ class RevisionEntryTest { foo${'\t'}ACC123${'\t'}bar """.trimIndent() val inputStream = ByteArrayInputStream(str.toByteArray()) - val entries = revisionEntryStreamAsSequence(inputStream, true).toList() + val entries = revisionEntryStreamAsSequence(inputStream).toList() assert(entries.size == 1) assert(entries[0].submissionId == "foo") assert(entries[0].accession == "ACC123") @@ -147,7 +147,7 @@ class RevisionEntryTest { foo${'\t'}bar """.trimIndent() val inputStream = ByteArrayInputStream(str.toByteArray()) - assertThrows { revisionEntryStreamAsSequence(inputStream, true).toList() } + assertThrows { revisionEntryStreamAsSequence(inputStream).toList() } } @Test @@ -159,7 +159,7 @@ class RevisionEntryTest { "123${'\t'}ACC123${'\t'}2025-01-01${'\t'}Switzerland Homo Sapiens Potter, H;\n" val inputStream = ByteArrayInputStream(str.toByteArray()) val exception = assertThrows { - revisionEntryStreamAsSequence(inputStream, true).toList() + revisionEntryStreamAsSequence(inputStream).toList() } assert(exception.message!!.contains("not a valid TSV file")) assert(exception.message!!.contains("Common causes include")) @@ -180,7 +180,7 @@ class RevisionEntryTest { val inputStream = ByteArrayInputStream(str.toByteArray()) val exception = assertThrows { - revisionEntryStreamAsSequence(inputStream, true).toList() + revisionEntryStreamAsSequence(inputStream).toList() } assert(exception.message!!.contains("not a valid TSV file")) assert(exception.message!!.contains("Common causes include")) @@ -194,7 +194,7 @@ class RevisionEntryTest { """.trimIndent() val inputStream = ByteArrayInputStream(str.toByteArray()) val exception = assertThrows { - revisionEntryStreamAsSequence(inputStream, true).toList() + revisionEntryStreamAsSequence(inputStream).toList() } assert(exception.message!!.contains("Record #1")) assert(exception.message!!.contains("contains no value for")) @@ -208,7 +208,7 @@ class RevisionEntryTest { """.trimIndent() val inputStream = ByteArrayInputStream(str.toByteArray()) val exception = assertThrows { - revisionEntryStreamAsSequence(inputStream, true).toList() + revisionEntryStreamAsSequence(inputStream).toList() } assert(exception.message!!.contains("Record #1")) assert(exception.message!!.contains("accession")) diff --git a/backend/src/test/resources/metadata_multi_segment.tsv b/backend/src/test/resources/metadata_multi_segment.tsv index 9c56b8cea9..4ffc6a9cf4 100644 --- a/backend/src/test/resources/metadata_multi_segment.tsv +++ b/backend/src/test/resources/metadata_multi_segment.tsv @@ -1,10 +1,10 @@ submissionId date region country division host fastaId custom0 2020-12-26 Europe Switzerland Bern Homo sapiens custom0_notOnlySegment -custom1 2020-12-15 Europe Switzerland Schaffhausen Homo sapiens custom1_notOnlySegment,custom1_secondSegment +custom1 2020-12-15 Europe Switzerland Schaffhausen Homo sapiens custom1_notOnlySegment custom1_secondSegment custom2 2020-12-02 Europe Switzerland Bern Homo sapiens custom2_notOnlySegment custom3 2020-12-25 Europe Switzerland Schaffhausen Homo sapiens custom3_notOnlySegment -custom4 2020-12-03 Europe Switzerland Zürich Homo sapiens custom4_notOnlySegment,custom4_secondSegment -custom5 2020-12-23 Europe Switzerland Basel-Land Homo sapiens custom5_notOnlySegment,custom5_secondSegment +custom4 2020-12-03 Europe Switzerland Zürich Homo sapiens custom4_notOnlySegment custom4_secondSegment +custom5 2020-12-23 Europe Switzerland Basel-Land Homo sapiens custom5_notOnlySegment custom5_secondSegment custom6 2020-12-16 Europe Switzerland Aargau Homo sapiens custom6_notOnlySegment custom7 2020-12-31 Europe Switzerland Sankt Gallen Homo sapiens custom7_notOnlySegment custom8 2020-12-16 Europe Switzerland Aargau Homo sapiens custom8_secondSegment diff --git a/backend/src/test/resources/revised_metadata_multi_segment.tsv b/backend/src/test/resources/revised_metadata_multi_segment.tsv index 290e5a3a57..b1be81dc9f 100644 --- a/backend/src/test/resources/revised_metadata_multi_segment.tsv +++ b/backend/src/test/resources/revised_metadata_multi_segment.tsv @@ -1,10 +1,10 @@ accession submissionId date region country division host fastaId 1 custom0 2020-12-26 Europe Switzerland Bern Homo sapiens custom0_notOnlySegment -2 custom1 2020-12-15 Europe Switzerland Schaffhausen Homo sapiens custom1_notOnlySegment,custom1_secondSegment +2 custom1 2020-12-15 Europe Switzerland Schaffhausen Homo sapiens custom1_notOnlySegment custom1_secondSegment 3 custom2 2020-12-02 Europe Switzerland Bern Homo sapiens custom2_notOnlySegment 4 custom3 2020-12-25 Europe Switzerland Schaffhausen Homo sapiens custom3_notOnlySegment -5 custom4 2020-12-03 Europe Switzerland Zürich Homo sapiens custom4_notOnlySegment,custom4_secondSegment -6 custom5 2020-12-23 Europe Switzerland Basel-Land Homo sapiens custom5_notOnlySegment,custom5_secondSegment +5 custom4 2020-12-03 Europe Switzerland Zürich Homo sapiens custom4_notOnlySegment custom4_secondSegment +6 custom5 2020-12-23 Europe Switzerland Basel-Land Homo sapiens custom5_notOnlySegment custom5_secondSegment 7 custom6 2020-12-16 Europe Switzerland Aargau Homo sapiens custom6_notOnlySegment 8 custom7 2020-12-31 Europe Switzerland Sankt Gallen Homo sapiens custom7_notOnlySegment 9 custom8 2020-12-16 Europe Switzerland Aargau Homo sapiens custom8_secondSegment From 2c34e849f96e7cea4b2bdba7bfb64f2492cff961 Mon Sep 17 00:00:00 2001 From: "Anna (Anya) Parker" <50943381+anna-parker@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:33:10 +0100 Subject: [PATCH 14/44] use white space as a separator everywhere (#5501) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolves # ### Screenshot ### PR Checklist - [ ] All necessary documentation has been adapted. - [ ] The implemented feature is covered by appropriate, automated tests. - [ ] Any manual testing that has been done is documented (i.e. what exactly was tested?) 🚀 Preview: Add `preview` label to enable --- .../main/kotlin/org/loculus/backend/model/SubmitModel.kt | 8 +++++--- .../backend/controller/submission/ReviseEndpointTest.kt | 6 ++++-- .../submission/SubmitEndpointFileSharingTest.kt | 2 +- .../backend/controller/submission/SubmitEndpointTest.kt | 4 ++-- integration-tests/tests/specs/cli/end-to-end-flow.spec.ts | 4 ++-- integration-tests/tests/test-data/cchfv_test_metadata.tsv | 2 +- website/src/components/Edit/SequencesForm.tsx | 2 +- 7 files changed, 16 insertions(+), 12 deletions(-) diff --git a/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt b/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt index a2970af41d..2359e8b47f 100644 --- a/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt +++ b/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt @@ -353,14 +353,16 @@ class SubmitModel( if (metadataKeysNotInSequences.isNotEmpty() || sequenceKeysNotInMetadata.isNotEmpty()) { val metadataNotPresentErrorText = if (metadataKeysNotInSequences.isNotEmpty()) { "Metadata file contains ${metadataKeysNotInSequences.size} FASTA ids that are not present " + - "in the sequence file: " + metadataKeysNotInSequences.toList().joinToString(limit = 10) + "; " + "in the sequence file: " + metadataKeysNotInSequences.toList().joinToString(limit = 10) { + "'$it'" + } } else { "" } val sequenceNotPresentErrorText = if (sequenceKeysNotInMetadata.isNotEmpty()) { "Sequence file contains ${sequenceKeysNotInMetadata.size} FASTA ids that are not present " + "in the metadata file: " + - sequenceKeysNotInMetadata.toList().joinToString(limit = 10) + sequenceKeysNotInMetadata.toList().joinToString(limit = 10) { "'$it'" } } else { "" } @@ -373,7 +375,7 @@ class SubmitModel( if (filesKeysNotInMetadata.isNotEmpty()) { throw UnprocessableEntityException( "File upload contains ${filesKeysNotInMetadata.size} submissionIds that are not present in the " + - "metadata file: " + filesKeysNotInMetadata.toList().joinToString(limit = 10), + "metadata file: " + filesKeysNotInMetadata.toList().joinToString(limit = 10) { "'$it'" }, ) } } diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/ReviseEndpointTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/ReviseEndpointTest.kt index e6b5aaa6b6..228a31af4b 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/ReviseEndpointTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/ReviseEndpointTest.kt @@ -619,11 +619,13 @@ class ReviseEndpointTest( AC >notInMetadata AC + >notInMetadata2 + AC """.trimIndent(), ), status().isUnprocessableEntity, "Unprocessable Entity", - "Sequence file contains 1 FASTA ids that are not present in the metadata file: notInMetadata", + "Sequence file contains 2 FASTA ids that are not present in the metadata file: 'notInMetadata', 'notInMetadata2'", ), Arguments.of( "sequence file misses submissionIds", @@ -642,7 +644,7 @@ class ReviseEndpointTest( ), status().isUnprocessableEntity, "Unprocessable Entity", - "Metadata file contains 1 FASTA ids that are not present in the sequence file: notInSequences", + "Metadata file contains 1 FASTA ids that are not present in the sequence file: 'notInSequences'", ), Arguments.of( "metadata file misses accession header", diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointFileSharingTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointFileSharingTest.kt index d9f28fdfc0..0b80727d8a 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointFileSharingTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointFileSharingTest.kt @@ -91,7 +91,7 @@ class SubmitEndpointFileSharingTest( jsonPath( "\$.detail", ).value( - "File upload contains 1 submissionIds that are not present in the metadata file: foobar", + "File upload contains 1 submissionIds that are not present in the metadata file: 'foobar'", ), ) } diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointTest.kt index 4e0e08a0e1..1eb6d6895f 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointTest.kt @@ -437,7 +437,7 @@ class SubmitEndpointTest( ), status().isUnprocessableEntity, "Unprocessable Entity", - "Sequence file contains 1 FASTA ids that are not present in the metadata file: notInMetadata", + "Sequence file contains 1 FASTA ids that are not present in the metadata file: 'notInMetadata'", DEFAULT_ORGANISM, DataUseTerms.Open, ), @@ -458,7 +458,7 @@ class SubmitEndpointTest( ), status().isUnprocessableEntity, "Unprocessable Entity", - "Metadata file contains 1 FASTA ids that are not present in the sequence file: notInSequences", + "Metadata file contains 1 FASTA ids that are not present in the sequence file: 'notInSequences'", DEFAULT_ORGANISM, DataUseTerms.Open, ), diff --git a/integration-tests/tests/specs/cli/end-to-end-flow.spec.ts b/integration-tests/tests/specs/cli/end-to-end-flow.spec.ts index 1913789aa7..475b9b3e63 100644 --- a/integration-tests/tests/specs/cli/end-to-end-flow.spec.ts +++ b/integration-tests/tests/specs/cli/end-to-end-flow.spec.ts @@ -18,8 +18,8 @@ cliTest.describe('CLI End-to-End Submission Flow', () => { const submissionId2 = `cli_e2e_${timestamp}_002`; const testMetadata = `authorAffiliations\tauthors\tgeoLocCountry\thostNameScientific\thostTaxonId\tsampleCollectionDate\tspecimenCollectorSampleId\tsubmissionId\tfastaId -"National Institute of Health, Department of Virology"\t"Ammar, M.; Salman, M.; Umair, M.; Ali, Q.; Hakim, R.; Haider, S.A.; Jamal, Z."\tPakistan\tHomo sapiens\t9606\t2023-08-26\tCCHF/NIHPAK-19/2023\t${submissionId1}\t${submissionId1}_L, ${submissionId1}_M, ${submissionId1}_S -"Research Lab, University of Example"\t"Example, A.; Test, B.; Sample, C."\tColombia\tHomo sapiens\t9606\t2021-12-12\tXF499\t${submissionId2}\t${submissionId2}_L, ${submissionId2}_M, ${submissionId2}_S`; +"National Institute of Health, Department of Virology"\t"Ammar, M.; Salman, M.; Umair, M.; Ali, Q.; Hakim, R.; Haider, S.A.; Jamal, Z."\tPakistan\tHomo sapiens\t9606\t2023-08-26\tCCHF/NIHPAK-19/2023\t${submissionId1}\t${submissionId1}_L ${submissionId1}_M ${submissionId1}_S +"Research Lab, University of Example"\t"Example, A.; Test, B.; Sample, C."\tColombia\tHomo sapiens\t9606\t2021-12-12\tXF499\t${submissionId2}\t${submissionId2}_L ${submissionId2}_M ${submissionId2}_S`; const testSequences = `>${submissionId1}_L CCACATTGACACAGANAGCTCCAGTAGTGGTTCTCTGTCCTTATTAAACCATGGACTTCTTAAGAAACCTTGACTGGACTCAGGTGATTGCTAGTCAGTATGTGACCAATCCCAGGTTTAATATCTCTGATTACTTCGAGATTGTTCGACAGCCTGGTGACGGGAACTGTTTCTACCACAGTATAGCTGAGTTAACCATGCCCAACAAAACAGATCACTCATACCATAACATCAAACATCTGACTGAGGTGGCAGCACGGAAGTATTATCAGGAGGAGCCGGAGGCTAAGCTCATTGGCCTGAGTCTGGAAGACTATCTTAAGAGGATGCTATCTGACAACGAATGGGGATCGACTCTTGAGGCATCTATGTTGGCTAAGGAAATGGGTATTACTATCATCATTTGGACTGTTGCAGCCAGTGACGAAGTGGAAGCAGGCATAAAGTTTGGTGATGGTGATGTGTTTACAGCCGTGAATCTTCTGCACTCCGGACAGACACACTTTGATGCCCTCAGAATACTGCCNCANTTTGAGGCTGACACAAGAGAGNCCTTNAGTCTGGTAGACAANNTNATAGCTGTGGACCANNTGACCTCNTCTTCAAGTGATGAANTGCAGGACTANGAAGANCTTGCTTTAGCACTTACNAGNGCGGAAGAACCATNTAGACGGTCTAGCNTGGATGAGGTNACCCTNTCTAAGAAACAAGCAGAGNTATTGAGGCAGAAGGCATCTCAGTTGTCNAAACTGGTTAATAAAAGTCAGAACATACCGACTAGAGTTGGCAGGGTTCTGGACTGTATGTTTAACTGCAAACTATGTGTTGAAATATCAGCTGACACTCTAATTCTGCGACCAGAATCTAAAGAAAGAATTGG diff --git a/integration-tests/tests/test-data/cchfv_test_metadata.tsv b/integration-tests/tests/test-data/cchfv_test_metadata.tsv index 27b2ad474e..05be5a01e3 100644 --- a/integration-tests/tests/test-data/cchfv_test_metadata.tsv +++ b/integration-tests/tests/test-data/cchfv_test_metadata.tsv @@ -1,2 +1,2 @@ fastaId ampliconPcrPrimerScheme ampliconSize anatomicalMaterial anatomicalPart authorAffiliations authors bodyProduct breadthOfCoverage cellLine collectionDevice collectionMethod consensusSequenceSoftwareName consensusSequenceSoftwareVersion cultureId dehostingMethod depthOfCoverage diagnostic_measurement_method diagnosticMeasurementUnit diagnosticMeasurementValue diagnosticTargetGeneName diagnosticTargetPresence environmentalMaterial environmentalSite experimentalSpecimenRoleType exposureDetails exposureEvent exposureSetting foodProduct foodProductProperties geoLocAdmin1 geoLocAdmin2 geoLocCity geoLocCountry geoLocSite hostAge hostAgeBin hostDisease hostGender hostHealthOutcome hostHealthState hostNameCommon hostNameScientific hostOriginCountry hostRole hostTaxonId hostVaccinationStatus passageMethod passageNumber presamplingActivity previousInfectionDisease previousInfectionOrganism purposeOfSampling purposeOfSequencing qualityControlDetails qualityControlDetermination qualityControlIssues qualityControlMethodName qualityControlMethodVersion rawSequenceDataProcessingMethod referenceGenomeAccession sampleCollectionDate sampleReceivedDate sampleType sequencedByContactEmail sequencedByContactName sequencedByOrganization sequencingAssayType sequencingDate sequencingInstrument sequencingProtocol signsAndSymptoms specimenCollectorSampleId specimenProcessing specimenProcessingDetails bioprojectAccessions submissionId travelHistory versionComment -test_NIHPAK-19_L, test_NIHPAK-19_M, test_NIHPAK-19_S "National Institute of Health, Department of Virology" "Ammar, M.; Salman, M.; Umair, M.; Ali, Q.; Hakim, R.; Haider, S.A.; Jamal, Z." Hogwarts Pakistan Homo sapiens 9606 2023-08-26 CCHF/NIHPAK-19/2023 test_NIHPAK-19 "OR964915.1,OR964926.1,OR964937.1" +test_NIHPAK-19_L test_NIHPAK-19_M test_NIHPAK-19_S "National Institute of Health, Department of Virology" "Ammar, M.; Salman, M.; Umair, M.; Ali, Q.; Hakim, R.; Haider, S.A.; Jamal, Z." Hogwarts Pakistan Homo sapiens 9606 2023-08-26 CCHF/NIHPAK-19/2023 test_NIHPAK-19 "OR964915.1,OR964926.1,OR964937.1" diff --git a/website/src/components/Edit/SequencesForm.tsx b/website/src/components/Edit/SequencesForm.tsx index beb7813bed..06342b89eb 100644 --- a/website/src/components/Edit/SequencesForm.tsx +++ b/website/src/components/Edit/SequencesForm.tsx @@ -161,7 +161,7 @@ export class EditableSequences { getFastaIds(): string { const filledRows = this.rows.filter((row) => row.value !== null); - return filledRows.map((sequence) => sequence.fastaHeader).join(','); + return filledRows.map((sequence) => sequence.fastaHeader).join(' '); } getSequenceFasta(): File | undefined { From c27199bdc0ebc06862fc8a93f70f8c061e66b15b Mon Sep 17 00:00:00 2001 From: "Anna (Anya) Parker" <50943381+anna-parker@users.noreply.github.com> Date: Fri, 21 Nov 2025 14:43:34 +0100 Subject: [PATCH 15/44] feat: add validation of map, update integration tests and add prepro tests (#5500) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolves # - [ ] All necessary documentation has been adapted. - [ ] The implemented feature is covered by appropriate, automated tests. - [ ] Any manual testing that has been done is documented (i.e. what exactly was tested?) 🚀 Preview: https://test-improving-minimizer.loculus.org --- .../ProcessedSequenceEntryValidator.kt | 38 +++++++++++++++---- .../loculus/backend/utils/MetadataEntry.kt | 10 ++++- ingest/scripts/heuristic_group_segments.py | 2 +- .../expected_output_cchf/submit_metadata.tsv | 2 +- preprocessing/dummy/main.py | 4 +- .../nextclade/tests/multi_segment_config.yaml | 4 +- .../tests/test_nextclade_preprocessing.py | 17 ++++++++- 7 files changed, 60 insertions(+), 17 deletions(-) diff --git a/backend/src/main/kotlin/org/loculus/backend/service/submission/ProcessedSequenceEntryValidator.kt b/backend/src/main/kotlin/org/loculus/backend/service/submission/ProcessedSequenceEntryValidator.kt index 4024d5f16d..6e43c22168 100644 --- a/backend/src/main/kotlin/org/loculus/backend/service/submission/ProcessedSequenceEntryValidator.kt +++ b/backend/src/main/kotlin/org/loculus/backend/service/submission/ProcessedSequenceEntryValidator.kt @@ -232,14 +232,11 @@ class ProcessedSequenceEntryValidator(private val schema: Schema, private val re "alignedNucleotideSequences", ) - validateNoUnknownSegment( - processedData.sequenceNameToFastaHeaderMap, - "sequenceNameToFastaHeaderMap", - ) - - validateNoUnknownSegment( + validateNoUnknownSegmentAndMapKeysMatch( processedData.unalignedNucleotideSequences, + processedData.sequenceNameToFastaHeaderMap, "unalignedNucleotideSequences", + "sequenceNameToFastaHeaderMap", ) validateNoUnknownSegment( @@ -276,12 +273,37 @@ class ProcessedSequenceEntryValidator(private val schema: Schema, private val re } } - private fun validateNoUnknownSegment(dataToValidate: Map, sequenceGrouping: String) { + private fun validateNoUnknownSegment(dataToValidate: Map, sequenceGroupingName: String) { val unknownSegments = dataToValidate.keys.subtract(referenceGenome.nucleotideSequences.map { it.name }.toSet()) if (unknownSegments.isNotEmpty()) { val unknownSegmentsString = unknownSegments.sorted().joinToString(", ") throw ProcessingValidationException( - "Unknown segments in '$sequenceGrouping': $unknownSegmentsString.", + "Unknown segments in '$sequenceGroupingName': $unknownSegmentsString.", + ) + } + } + + private fun validateNoUnknownSegmentAndMapKeysMatch( + map1: Map, + map2: Map, + map1Name: String, + map2Name: String, + ) { + validateNoUnknownSegment(map1, map1Name) + validateNoUnknownSegment(map2, map2Name) + + val keysMissingFromMap2 = map1.keys.subtract(map2.keys) + if (keysMissingFromMap2.isNotEmpty()) { + val missingString = keysMissingFromMap2.sorted().joinToString(", ") + throw ProcessingValidationException( + "'$map2Name': is missing the keys '$missingString'.", + ) + } + val keysMissingFromMap1 = map2.keys.subtract(map1.keys) + if (keysMissingFromMap1.isNotEmpty()) { + val missingString = keysMissingFromMap1.sorted().joinToString(", ") + throw ProcessingValidationException( + "'$map1Name': is missing the keys '$missingString'.", ) } } diff --git a/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt b/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt index 52d975b530..46d0c66c6a 100644 --- a/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt +++ b/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt @@ -97,7 +97,10 @@ fun metadataEntryStreamAsSequence(metadataInputStream: InputStream): Sequence list[Sequence]: processed = [] for sequence in unprocessed: metadata = sequence.data.get("metadata", {}) + submissionId = metadata.get("submissionId", "unknown_submission") if not disableConsensusSequences: metadata["pangoLineage"] = random.choice(possible_lineages) @@ -157,11 +158,12 @@ def process(unprocessed: list[Sequence]) -> list[Sequence]: "unalignedNucleotideSequences": {}, "alignedAminoAcidSequences": {}, "nucleotideInsertions": {}, - "aminoAcidInsertions": {} + "aminoAcidInsertions": {}, } if not disableConsensusSequences: data = {**data, **mock_sequences} + data["sequenceNameToFastaHeaderMap"] = {"main": submissionId} updated_sequence = Sequence( sequence.accession, diff --git a/preprocessing/nextclade/tests/multi_segment_config.yaml b/preprocessing/nextclade/tests/multi_segment_config.yaml index eb4d5099d7..5a8367e462 100644 --- a/preprocessing/nextclade/tests/multi_segment_config.yaml +++ b/preprocessing/nextclade/tests/multi_segment_config.yaml @@ -11,10 +11,10 @@ nextclade_dataset_server: TEST nucleotideSequences: - name: ebola-sudan nextclade_dataset_name: ebola-dataset/ebola-sudan - accepted_sort_matches: ebola-sudan + accepted_sort_matches: [accepted-name] - name: ebola-zaire nextclade_dataset_name: ebola-dataset/ebola-zaire - accepted_sort_matches: ebola-zaire + accepted_sort_matches: [accepted-name] organism: multi-ebola-test processing_spec: totalInsertedNucs_ebola-zaire: diff --git a/preprocessing/nextclade/tests/test_nextclade_preprocessing.py b/preprocessing/nextclade/tests/test_nextclade_preprocessing.py index b9b40b7341..48b3bcd96e 100644 --- a/preprocessing/nextclade/tests/test_nextclade_preprocessing.py +++ b/preprocessing/nextclade/tests/test_nextclade_preprocessing.py @@ -310,14 +310,20 @@ def invalid_sequence() -> str: "ebola-sudan": consensus_sequence("ebola-sudan"), "ebola-zaire": consensus_sequence("ebola-zaire"), }, - nucleotideInsertions={"ebola-sudan": ["2671:GAC"], "ebola-zaire": ["11097:GAC"]}, + nucleotideInsertions={ + "ebola-sudan": ["2671:GAC"], + "ebola-zaire": ["11097:GAC"], + }, alignedAminoAcidSequences={ "NPEbolaSudan": ebola_sudan_aa(consensus_sequence("single"), "NP"), "VP35EbolaSudan": ebola_sudan_aa(consensus_sequence("single"), "VP35"), "VP24EbolaZaire": ebola_zaire_aa(consensus_sequence("ebola-zaire"), "VP24"), "LEbolaZaire": ebola_zaire_aa(consensus_sequence("ebola-zaire"), "L"), }, - aminoAcidInsertions={"NPEbolaSudan": ["738:D"], "VP24EbolaZaire": ["251:D"]}, + aminoAcidInsertions={ + "NPEbolaSudan": ["738:D"], + "VP24EbolaZaire": ["251:D"], + }, sequenceNameToFastaHeaderMap={ "ebola-sudan": "fastaHeader1", "ebola-zaire": "fastaHeader2", @@ -831,6 +837,13 @@ def test_preprocessing_multi_segment_none_requirement(test_case_def: Case): ) +def test_config_accepted_sort_matches() -> None: + config = get_config(MULTI_SEGMENT_CONFIG, ignore_args=True) + accepted_fields = config.nucleotideSequences[0].accepted_sort_matches + expected_fields = {"ebola-dataset/ebola-sudan", "ebola-sudan", "accepted-name"} + assert set(accepted_fields) == expected_fields + + def test_preprocessing_without_metadata() -> None: config = get_config(MULTI_SEGMENT_CONFIG, ignore_args=True) sequence_entry_data = UnprocessedEntry( From ad97da875fed4df9aae0a24b399b809d229d2b0e Mon Sep 17 00:00:00 2001 From: anna-parker <50943381+anna-parker@users.noreply.github.com> Date: Fri, 21 Nov 2025 15:13:42 +0100 Subject: [PATCH 16/44] feat: rename sequenceNameToFastaHeaderMap to sequenceNameToFastaId --- .../loculus/backend/api/SubmissionTypes.kt | 2 +- .../service/submission/CompressionService.kt | 4 +-- .../submission/EmptyProcessedDataProvider.kt | 2 +- .../ProcessedSequenceEntryValidator.kt | 4 +-- .../submission/SubmissionDatabaseService.kt | 2 +- .../submission/PreparedProcessedData.kt | 6 ++-- .../ProcessedMetadataPostprocessorTest.kt | 2 +- .../ProcessedSequencesPostprocessorTest.kt | 2 +- .../utils/EarliestReleaseDateFinderTest.kt | 2 +- preprocessing/dummy/main.py | 2 +- .../src/loculus_preprocessing/datatypes.py | 6 ++-- .../src/loculus_preprocessing/nextclade.py | 28 ++++++++-------- .../src/loculus_preprocessing/prepro.py | 11 +++---- .../nextclade/tests/factory_methods.py | 10 +++--- .../tests/test_nextclade_preprocessing.py | 32 +++++++++---------- preprocessing/specification.md | 2 +- website/src/components/Edit/SequencesForm.tsx | 4 +-- .../ReviewPage/SequencesDialog.spec.tsx | 2 +- website/src/types/backend.ts | 2 +- website/vitest.setup.ts | 2 +- 20 files changed, 62 insertions(+), 65 deletions(-) diff --git a/backend/src/main/kotlin/org/loculus/backend/api/SubmissionTypes.kt b/backend/src/main/kotlin/org/loculus/backend/api/SubmissionTypes.kt index 6352e85723..b9a99a0249 100644 --- a/backend/src/main/kotlin/org/loculus/backend/api/SubmissionTypes.kt +++ b/backend/src/main/kotlin/org/loculus/backend/api/SubmissionTypes.kt @@ -171,7 +171,7 @@ data class ProcessedData( example = """{"segment1": "fastaHeader1", "segment2": "fastaHeader2"}""", description = "The key is the segment name, the value is the fastaHeader of the original Data", ) - val sequenceNameToFastaHeaderMap: Map = emptyMap(), + val sequenceNameToFastaId: Map = emptyMap(), @Schema( example = """{"raw_reads": [{"fileId": "s0m3-uUiDd", "name": "data.fastaq"}], "sequencing_logs": []}""", description = "The key is the file category name, the value is a list of files, with ID and name.", diff --git a/backend/src/main/kotlin/org/loculus/backend/service/submission/CompressionService.kt b/backend/src/main/kotlin/org/loculus/backend/service/submission/CompressionService.kt index c9719082a4..1ebd5b8e1c 100644 --- a/backend/src/main/kotlin/org/loculus/backend/service/submission/CompressionService.kt +++ b/backend/src/main/kotlin/org/loculus/backend/service/submission/CompressionService.kt @@ -102,7 +102,7 @@ class CompressionService(private val compressionDictService: CompressionDictServ } }, processedData.aminoAcidInsertions, - processedData.sequenceNameToFastaHeaderMap, + processedData.sequenceNameToFastaId, processedData.files, ) @@ -129,7 +129,7 @@ class CompressionService(private val compressionDictService: CompressionDictServ } }, processedData.aminoAcidInsertions, - processedData.sequenceNameToFastaHeaderMap, + processedData.sequenceNameToFastaId, processedData.files, ) diff --git a/backend/src/main/kotlin/org/loculus/backend/service/submission/EmptyProcessedDataProvider.kt b/backend/src/main/kotlin/org/loculus/backend/service/submission/EmptyProcessedDataProvider.kt index f715554f08..8de2774f41 100644 --- a/backend/src/main/kotlin/org/loculus/backend/service/submission/EmptyProcessedDataProvider.kt +++ b/backend/src/main/kotlin/org/loculus/backend/service/submission/EmptyProcessedDataProvider.kt @@ -20,7 +20,7 @@ class EmptyProcessedDataProvider(private val backendConfig: BackendConfig) { alignedAminoAcidSequences = referenceGenome.genes.map { it.name }.associateWith { null }, nucleotideInsertions = referenceGenome.nucleotideSequences.map { it.name }.associateWith { emptyList() }, aminoAcidInsertions = referenceGenome.genes.map { it.name }.associateWith { emptyList() }, - sequenceNameToFastaHeaderMap = referenceGenome.nucleotideSequences.map { it.name }.associateWith { "" }, + sequenceNameToFastaId = referenceGenome.nucleotideSequences.map { it.name }.associateWith { "" }, files = null, ) } diff --git a/backend/src/main/kotlin/org/loculus/backend/service/submission/ProcessedSequenceEntryValidator.kt b/backend/src/main/kotlin/org/loculus/backend/service/submission/ProcessedSequenceEntryValidator.kt index 6e43c22168..e803ddbb95 100644 --- a/backend/src/main/kotlin/org/loculus/backend/service/submission/ProcessedSequenceEntryValidator.kt +++ b/backend/src/main/kotlin/org/loculus/backend/service/submission/ProcessedSequenceEntryValidator.kt @@ -234,9 +234,9 @@ class ProcessedSequenceEntryValidator(private val schema: Schema, private val re validateNoUnknownSegmentAndMapKeysMatch( processedData.unalignedNucleotideSequences, - processedData.sequenceNameToFastaHeaderMap, + processedData.sequenceNameToFastaId, "unalignedNucleotideSequences", - "sequenceNameToFastaHeaderMap", + "sequenceNameToFastaId", ) validateNoUnknownSegment( diff --git a/backend/src/main/kotlin/org/loculus/backend/service/submission/SubmissionDatabaseService.kt b/backend/src/main/kotlin/org/loculus/backend/service/submission/SubmissionDatabaseService.kt index b5442ab685..27aef4fddc 100644 --- a/backend/src/main/kotlin/org/loculus/backend/service/submission/SubmissionDatabaseService.kt +++ b/backend/src/main/kotlin/org/loculus/backend/service/submission/SubmissionDatabaseService.kt @@ -457,7 +457,7 @@ class SubmissionDatabaseService( aminoAcidInsertions = processedData.aminoAcidInsertions.mapValues { (_, it) -> it.map { insertion -> insertion.copy(sequence = insertion.sequence.uppercase(Locale.US)) } }, - sequenceNameToFastaHeaderMap = processedData.sequenceNameToFastaHeaderMap, + sequenceNameToFastaId = processedData.sequenceNameToFastaId, ) private fun validateExternalMetadata( diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/PreparedProcessedData.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/PreparedProcessedData.kt index d5523ac3af..4eadafcf29 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/PreparedProcessedData.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/PreparedProcessedData.kt @@ -62,7 +62,7 @@ val defaultProcessedData = ProcessedData( Insertion(123, "RN"), ), ), - sequenceNameToFastaHeaderMap = mapOf(MAIN_SEGMENT to "header"), + sequenceNameToFastaId = mapOf(MAIN_SEGMENT to "header"), files = null, ) @@ -102,7 +102,7 @@ val defaultProcessedDataMultiSegmented = ProcessedData( Insertion(123, "RN"), ), ), - sequenceNameToFastaHeaderMap = mapOf("notOnlySegment" to "header1", "secondSegment" to "header2"), + sequenceNameToFastaId = mapOf("notOnlySegment" to "header1", "secondSegment" to "header2"), files = null, ) @@ -119,7 +119,7 @@ val defaultProcessedDataWithoutSequences = ProcessedData( nucleotideInsertions = emptyMap(), alignedAminoAcidSequences = emptyMap(), aminoAcidInsertions = emptyMap(), - sequenceNameToFastaHeaderMap = emptyMap(), + sequenceNameToFastaId = emptyMap(), files = null, ) diff --git a/backend/src/test/kotlin/org/loculus/backend/service/ProcessedMetadataPostprocessorTest.kt b/backend/src/test/kotlin/org/loculus/backend/service/ProcessedMetadataPostprocessorTest.kt index 6ecbb08e79..2213082e02 100644 --- a/backend/src/test/kotlin/org/loculus/backend/service/ProcessedMetadataPostprocessorTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/service/ProcessedMetadataPostprocessorTest.kt @@ -43,7 +43,7 @@ class ProcessedMetadataPostprocessorTest( nucleotideInsertions = emptyMap(), alignedAminoAcidSequences = emptyMap(), aminoAcidInsertions = emptyMap(), - sequenceNameToFastaHeaderMap = emptyMap(), + sequenceNameToFastaId = emptyMap(), files = null, ) diff --git a/backend/src/test/kotlin/org/loculus/backend/service/ProcessedSequencesPostprocessorTest.kt b/backend/src/test/kotlin/org/loculus/backend/service/ProcessedSequencesPostprocessorTest.kt index 51e6db7353..5d77d31d45 100644 --- a/backend/src/test/kotlin/org/loculus/backend/service/ProcessedSequencesPostprocessorTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/service/ProcessedSequencesPostprocessorTest.kt @@ -91,7 +91,7 @@ class ProcessedSequencesPostprocessorTest( unconfiguredPresentGene to listOf(Insertion(13, "TT")), unconfiguredNullGene to emptyList(), ), - sequenceNameToFastaHeaderMap = mapOf( + sequenceNameToFastaId = mapOf( "configuredPresentSeg" to "header1", "unconfiguredPresentSeg" to "header2", ), diff --git a/backend/src/test/kotlin/org/loculus/backend/utils/EarliestReleaseDateFinderTest.kt b/backend/src/test/kotlin/org/loculus/backend/utils/EarliestReleaseDateFinderTest.kt index 43e89af3a4..342bfc1bd7 100644 --- a/backend/src/test/kotlin/org/loculus/backend/utils/EarliestReleaseDateFinderTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/utils/EarliestReleaseDateFinderTest.kt @@ -62,7 +62,7 @@ fun row( nucleotideInsertions = emptyMap(), alignedAminoAcidSequences = emptyMap(), aminoAcidInsertions = emptyMap(), - sequenceNameToFastaHeaderMap = emptyMap(), + sequenceNameToFastaId = emptyMap(), files = null, ), isRevocation = false, diff --git a/preprocessing/dummy/main.py b/preprocessing/dummy/main.py index d5ec0b5c76..e3a2b9cc14 100644 --- a/preprocessing/dummy/main.py +++ b/preprocessing/dummy/main.py @@ -163,7 +163,7 @@ def process(unprocessed: list[Sequence]) -> list[Sequence]: if not disableConsensusSequences: data = {**data, **mock_sequences} - data["sequenceNameToFastaHeaderMap"] = {"main": submissionId} + data["sequenceNameToFastaId"] = {"main": submissionId} updated_sequence = Sequence( sequence.accession, diff --git a/preprocessing/nextclade/src/loculus_preprocessing/datatypes.py b/preprocessing/nextclade/src/loculus_preprocessing/datatypes.py index 740153dd81..2424269a87 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/datatypes.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/datatypes.py @@ -92,7 +92,7 @@ class UnprocessedEntry: @dataclass class SegmentAssignment: unalignedNucleotideSequences: dict[SegmentName, NucleotideSequence | None] # noqa: N815 - sequenceNameToFastaHeaderMap: dict[SegmentName, str] # noqa: N815 + sequenceNameToFastaId: dict[SegmentName, str] # noqa: N815 errors: list[ProcessingAnnotation] warnings: list[ProcessingAnnotation] @@ -116,7 +116,7 @@ class UnprocessedAfterNextclade: nucleotideInsertions: dict[SegmentName, list[NucleotideInsertion]] # noqa: N815 alignedAminoAcidSequences: dict[GeneName, AminoAcidSequence | None] # noqa: N815 aminoAcidInsertions: dict[GeneName, list[AminoAcidInsertion]] # noqa: N815 - sequenceNameToFastaHeaderMap: dict[SegmentName, str] # noqa: N815 + sequenceNameToFastaId: dict[SegmentName, str] # noqa: N815 errors: list[ProcessingAnnotation] warnings: list[ProcessingAnnotation] @@ -135,7 +135,7 @@ class ProcessedData: nucleotideInsertions: dict[SegmentName, Any] # noqa: N815 alignedAminoAcidSequences: dict[GeneName, Any] # noqa: N815 aminoAcidInsertions: dict[GeneName, Any] # noqa: N815 - sequenceNameToFastaHeaderMap: dict[SegmentName, str] # noqa: N815 + sequenceNameToFastaId: dict[SegmentName, str] # noqa: N815 files: dict[str, list[FileIdAndName]] | None = None diff --git a/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py b/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py index 1a1c0dd0ad..846cf11b7e 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py @@ -369,7 +369,7 @@ def assign_segment_with_nextclade_sort( message=msg, ) ) - sequenceNameToFastaHeaderMap: dict[SegmentName, str] = {} + sequenceNameToFastaId: dict[SegmentName, str] = {} for segment_name, headers in sort_results_map.items(): if len(headers) > 1: msg = ( @@ -385,7 +385,7 @@ def assign_segment_with_nextclade_sort( ) ) continue - sequenceNameToFastaHeaderMap[segment_name] = headers[0] + sequenceNameToFastaId[segment_name] = headers[0] unaligned_nucleotide_sequences[segment_name] = input_unaligned_sequences[headers[0]] if ( @@ -403,7 +403,7 @@ def assign_segment_with_nextclade_sort( return SegmentAssignment( unalignedNucleotideSequences=unaligned_nucleotide_sequences, - sequenceNameToFastaHeaderMap=sequenceNameToFastaHeaderMap, + sequenceNameToFastaId=sequenceNameToFastaId, errors=errors, warnings=warnings, ) @@ -416,7 +416,7 @@ def assign_single_segment( errors: list[ProcessingAnnotation] = [] warnings: list[ProcessingAnnotation] = [] unaligned_nucleotide_sequences: dict[SegmentName, NucleotideSequence | None] = {} - sequenceNameToFastaHeaderMap: dict[SegmentName, str] = {} + sequenceNameToFastaId: dict[SegmentName, str] = {} if len(input_unaligned_sequences) > 1: errors.append( ProcessingAnnotation.from_single( @@ -433,11 +433,11 @@ def assign_single_segment( ) else: fastaHeader, value = next(iter(input_unaligned_sequences.items())) - sequenceNameToFastaHeaderMap["main"] = fastaHeader + sequenceNameToFastaId["main"] = fastaHeader unaligned_nucleotide_sequences["main"] = value return SegmentAssignment( unalignedNucleotideSequences=unaligned_nucleotide_sequences, - sequenceNameToFastaHeaderMap=sequenceNameToFastaHeaderMap, + sequenceNameToFastaId=sequenceNameToFastaId, errors=errors, warnings=warnings, ) @@ -450,12 +450,12 @@ def assign_segment_using_header( errors: list[ProcessingAnnotation] = [] warnings: list[ProcessingAnnotation] = [] unaligned_nucleotide_sequences: dict[SegmentName, NucleotideSequence | None] = {} - sequenceNameToFastaHeaderMap: dict[SegmentName, str] = {} + sequenceNameToFastaId: dict[SegmentName, str] = {} duplicate_segments = set() if not config.nucleotideSequences: return SegmentAssignment( unalignedNucleotideSequences={}, - sequenceNameToFastaHeaderMap={}, + sequenceNameToFastaId={}, errors=errors, warnings=warnings, ) @@ -485,13 +485,13 @@ def assign_segment_using_header( ) ) elif len(unaligned_segment) == 1: - sequenceNameToFastaHeaderMap[segment] = unaligned_segment[0] + sequenceNameToFastaId[segment] = unaligned_segment[0] unaligned_nucleotide_sequences[segment] = input_unaligned_sequences[ unaligned_segment[0] ] remaining_segments = ( set(input_unaligned_sequences.keys()) - - set(sequenceNameToFastaHeaderMap.values()) + - set(sequenceNameToFastaId.values()) - duplicate_segments ) if len(remaining_segments) > 0: @@ -518,7 +518,7 @@ def assign_segment_using_header( ) return SegmentAssignment( unalignedNucleotideSequences=unaligned_nucleotide_sequences, - sequenceNameToFastaHeaderMap=sequenceNameToFastaHeaderMap, + sequenceNameToFastaId=sequenceNameToFastaId, errors=errors, warnings=warnings, ) @@ -538,7 +538,7 @@ def enrich_with_nextclade( # noqa: C901, PLR0914, PLR0915 nucleotideInsertions: dict[SegmentName, list[NucleotideInsertion]] alignedAminoAcidSequences: dict[GeneName, AminoAcidSequence | None] aminoAcidInsertions: dict[GeneName, list[AminoAcidInsertion]] - sequenceNameToFastaHeaderMap: dict[SegmentName, str] + sequenceNameToFastaId: dict[SegmentName, str] )` object. """ unaligned_nucleotide_sequences: dict[ @@ -576,7 +576,7 @@ def enrich_with_nextclade( # noqa: C901, PLR0914, PLR0915 unaligned_nucleotide_sequences[id] = segment_assignment.unalignedNucleotideSequences alerts.errors[id] = segment_assignment.errors alerts.warnings[id] = segment_assignment.warnings - segment_assignment_map[id] = segment_assignment.sequenceNameToFastaHeaderMap + segment_assignment_map[id] = segment_assignment.sequenceNameToFastaId nextclade_metadata: defaultdict[ AccessionVersion, defaultdict[SegmentName, dict[str, Any] | None] @@ -681,7 +681,7 @@ def enrich_with_nextclade( # noqa: C901, PLR0914, PLR0915 nucleotideInsertions=nucleotide_insertions[id], alignedAminoAcidSequences=aligned_aminoacid_sequences[id], aminoAcidInsertions=amino_acid_insertions[id], - sequenceNameToFastaHeaderMap=segment_assignment_map[id], + sequenceNameToFastaId=segment_assignment_map[id], errors=alerts.errors[id], warnings=alerts.warnings[id], ) diff --git a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py index aa5b39a2a1..eb69ad4e57 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py @@ -56,7 +56,6 @@ logger = logging.getLogger(__name__) - def accession_from_str(id_str: AccessionVersion) -> str: return id_str.split(".")[0] @@ -203,7 +202,7 @@ def processed_entry_no_alignment( output_metadata: ProcessedMetadata, errors: list[ProcessingAnnotation], warnings: list[ProcessingAnnotation], - sequenceNameToFastaHeaderMap: dict[SegmentName, str], + sequenceNameToFastaId: dict[SegmentName, str], ) -> SubmissionData: """Process a single sequence without alignment""" @@ -223,7 +222,7 @@ def processed_entry_no_alignment( nucleotideInsertions=nucleotide_insertions, alignedAminoAcidSequences=aligned_aminoacid_sequences, aminoAcidInsertions=amino_acid_insertions, - sequenceNameToFastaHeaderMap=sequenceNameToFastaHeaderMap, + sequenceNameToFastaId=sequenceNameToFastaId, ), errors=errors, warnings=warnings, @@ -481,7 +480,7 @@ def process_single( nucleotideInsertions=unprocessed.nucleotideInsertions, alignedAminoAcidSequences=unprocessed.alignedAminoAcidSequences, aminoAcidInsertions=unprocessed.aminoAcidInsertions, - sequenceNameToFastaHeaderMap=unprocessed.sequenceNameToFastaHeaderMap, + sequenceNameToFastaId=unprocessed.sequenceNameToFastaId, ), errors=list(set(unprocessed.errors + iupac_errors + alignment_errors + metadata_errors)), warnings=list(set(unprocessed.warnings + alignment_warnings + metadata_warnings)), @@ -518,7 +517,7 @@ def process_single_unaligned( output_metadata=output_metadata, errors=list(set(iupac_errors + metadata_errors + segment_assignment.errors)), warnings=list(set(metadata_warnings)), - sequenceNameToFastaHeaderMap=segment_assignment.sequenceNameToFastaHeaderMap, + sequenceNameToFastaId=segment_assignment.sequenceNameToFastaId, ) @@ -534,7 +533,7 @@ def processed_entry_with_errors(id) -> SubmissionData: nucleotideInsertions=defaultdict(dict[str, Any]), alignedAminoAcidSequences=defaultdict(dict[str, Any]), aminoAcidInsertions=defaultdict(dict[str, Any]), - sequenceNameToFastaHeaderMap=defaultdict(str), + sequenceNameToFastaId=defaultdict(str), ), errors=[ ProcessingAnnotation.from_single( diff --git a/preprocessing/nextclade/tests/factory_methods.py b/preprocessing/nextclade/tests/factory_methods.py index 82530b49f4..4d64b334b7 100644 --- a/preprocessing/nextclade/tests/factory_methods.py +++ b/preprocessing/nextclade/tests/factory_methods.py @@ -54,7 +54,7 @@ class ProcessedAlignment: nucleotideInsertions: dict[str, list[str]] = field(default_factory=dict) # noqa: N815 alignedAminoAcidSequences: dict[str, str | None] = field(default_factory=dict) # noqa: N815 aminoAcidInsertions: dict[str, list[str]] = field(default_factory=dict) # noqa: N815 - sequenceNameToFastaHeaderMap: dict[str, str] = field( # noqa: N815 + sequenceNameToFastaId: dict[str, str] = field( # noqa: N815 default_factory=dict ) @@ -140,7 +140,7 @@ def create_processed_entry( nucleotideInsertions=processed_alignment.nucleotideInsertions, alignedAminoAcidSequences=processed_alignment.alignedAminoAcidSequences, aminoAcidInsertions=processed_alignment.aminoAcidInsertions, - sequenceNameToFastaHeaderMap=processed_alignment.sequenceNameToFastaHeaderMap, + sequenceNameToFastaId=processed_alignment.sequenceNameToFastaId, ), errors=errors, warnings=warnings, @@ -247,7 +247,7 @@ def verify_processed_entry( f"{test_name}: amino acid insertions '{actual.aminoAcidInsertions}' do not " f"match expectation '{expected.aminoAcidInsertions}'." ) - assert actual.sequenceNameToFastaHeaderMap == expected.sequenceNameToFastaHeaderMap, ( - f"{test_name}: sequence name to fasta header map '{actual.sequenceNameToFastaHeaderMap}' do not " - f"match expectation '{expected.sequenceNameToFastaHeaderMap}'." + assert actual.sequenceNameToFastaId == expected.sequenceNameToFastaId, ( + f"{test_name}: sequence name to fasta header map '{actual.sequenceNameToFastaId}' do not " + f"match expectation '{expected.sequenceNameToFastaId}'." ) diff --git a/preprocessing/nextclade/tests/test_nextclade_preprocessing.py b/preprocessing/nextclade/tests/test_nextclade_preprocessing.py index 48b3bcd96e..99130ce646 100644 --- a/preprocessing/nextclade/tests/test_nextclade_preprocessing.py +++ b/preprocessing/nextclade/tests/test_nextclade_preprocessing.py @@ -141,7 +141,7 @@ def invalid_sequence() -> str: "VP35EbolaSudan": ebola_sudan_aa(sequence_with_mutation("single"), "VP35"), }, aminoAcidInsertions={}, - sequenceNameToFastaHeaderMap={"main": "fastaHeader"}, + sequenceNameToFastaId={"main": "fastaHeader"}, ), ), Case( @@ -168,7 +168,7 @@ def invalid_sequence() -> str: "VP35EbolaSudan": ebola_sudan_aa(consensus_sequence("single"), "VP35"), }, aminoAcidInsertions={"NPEbolaSudan": ["738:D"]}, - sequenceNameToFastaHeaderMap={"main": "fastaHeader"}, + sequenceNameToFastaId={"main": "fastaHeader"}, ), ), Case( @@ -199,7 +199,7 @@ def invalid_sequence() -> str: ), }, aminoAcidInsertions={}, - sequenceNameToFastaHeaderMap={"main": "fastaHeader"}, + sequenceNameToFastaId={"main": "fastaHeader"}, ), ), Case( @@ -232,7 +232,7 @@ def invalid_sequence() -> str: nucleotideInsertions={}, alignedAminoAcidSequences={}, aminoAcidInsertions={}, - sequenceNameToFastaHeaderMap={"main": "fastaHeader"}, + sequenceNameToFastaId={"main": "fastaHeader"}, ), ), ] @@ -275,7 +275,7 @@ def invalid_sequence() -> str: "LEbolaZaire": ebola_zaire_aa(sequence_with_mutation("ebola-zaire"), "L"), }, aminoAcidInsertions={}, - sequenceNameToFastaHeaderMap={ + sequenceNameToFastaId={ "ebola-sudan": "fastaHeader1", "ebola-zaire": "fastaHeader2", }, @@ -324,7 +324,7 @@ def invalid_sequence() -> str: "NPEbolaSudan": ["738:D"], "VP24EbolaZaire": ["251:D"], }, - sequenceNameToFastaHeaderMap={ + sequenceNameToFastaId={ "ebola-sudan": "fastaHeader1", "ebola-zaire": "fastaHeader2", }, @@ -377,7 +377,7 @@ def invalid_sequence() -> str: ), }, aminoAcidInsertions={}, - sequenceNameToFastaHeaderMap={ + sequenceNameToFastaId={ "ebola-sudan": "fastaHeader1", "ebola-zaire": "fastaHeader2", }, @@ -415,7 +415,7 @@ def invalid_sequence() -> str: "LEbolaZaire": ebola_zaire_aa(sequence_with_mutation("ebola-zaire"), "L"), }, aminoAcidInsertions={}, - sequenceNameToFastaHeaderMap={"ebola-zaire": "fastaHeader2"}, + sequenceNameToFastaId={"ebola-zaire": "fastaHeader2"}, ), ), ] @@ -453,7 +453,7 @@ def invalid_sequence() -> str: nucleotideInsertions={}, alignedAminoAcidSequences={}, aminoAcidInsertions={}, - sequenceNameToFastaHeaderMap={}, + sequenceNameToFastaId={}, ), ), Case( @@ -498,7 +498,7 @@ def invalid_sequence() -> str: "LEbolaZaire": ebola_zaire_aa(sequence_with_mutation("ebola-zaire"), "L"), }, aminoAcidInsertions={}, - sequenceNameToFastaHeaderMap={"ebola-zaire": "fastaHeader2"}, + sequenceNameToFastaId={"ebola-zaire": "fastaHeader2"}, ), ), ] @@ -545,7 +545,7 @@ def invalid_sequence() -> str: nucleotideInsertions={}, alignedAminoAcidSequences={}, aminoAcidInsertions={}, - sequenceNameToFastaHeaderMap={}, + sequenceNameToFastaId={}, ), ), Case( @@ -590,7 +590,7 @@ def invalid_sequence() -> str: "LEbolaZaire": ebola_zaire_aa(sequence_with_mutation("ebola-zaire"), "L"), }, aminoAcidInsertions={}, - sequenceNameToFastaHeaderMap={"ebola-zaire": "fastaHeader2"}, + sequenceNameToFastaId={"ebola-zaire": "fastaHeader2"}, ), ), ] @@ -624,7 +624,7 @@ def invalid_sequence() -> str: nucleotideInsertions={}, alignedAminoAcidSequences={}, aminoAcidInsertions={}, - sequenceNameToFastaHeaderMap={}, + sequenceNameToFastaId={}, ), ), ] @@ -691,7 +691,7 @@ def invalid_sequence() -> str: nucleotideInsertions={}, alignedAminoAcidSequences={}, aminoAcidInsertions={}, - sequenceNameToFastaHeaderMap={ + sequenceNameToFastaId={ "ebola-sudan": "prefix_ebola-sudan", "ebola-zaire": "other_prefix_ebola-zaire", }, @@ -726,7 +726,7 @@ def invalid_sequence() -> str: nucleotideInsertions={}, alignedAminoAcidSequences={}, aminoAcidInsertions={}, - sequenceNameToFastaHeaderMap={}, + sequenceNameToFastaId={}, ), ), Case( @@ -759,7 +759,7 @@ def invalid_sequence() -> str: nucleotideInsertions={}, alignedAminoAcidSequences={}, aminoAcidInsertions={}, - sequenceNameToFastaHeaderMap={"ebola-sudan": "ebola-sudan"}, + sequenceNameToFastaId={"ebola-sudan": "ebola-sudan"}, ), ), ] diff --git a/preprocessing/specification.md b/preprocessing/specification.md index 07532a2f9a..54bc84119a 100644 --- a/preprocessing/specification.md +++ b/preprocessing/specification.md @@ -92,7 +92,7 @@ In the NDJSON, each row contains a sequence entry version and a list of errors a nucleotideInsertions, alignedAminoAcidSequences, aminoAcidInsertions, - sequenceNameToFastaHeaderMap, //TODO: use in backend + sequenceNameToFastaId, files } } diff --git a/website/src/components/Edit/SequencesForm.tsx b/website/src/components/Edit/SequencesForm.tsx index 06342b89eb..3799f25961 100644 --- a/website/src/components/Edit/SequencesForm.tsx +++ b/website/src/components/Edit/SequencesForm.tsx @@ -82,9 +82,7 @@ export class EditableSequences { referenceGenomeLightweightSchema: ReferenceGenomesLightweightSchema, ): EditableSequences { const maxNumberRows = this.getMaxNumberOfRows(referenceGenomeLightweightSchema); - const fastaHeaderMap = EditableSequences.invertRecordMulti( - initialData.processedData.sequenceNameToFastaHeaderMap, - ); + const fastaHeaderMap = EditableSequences.invertRecordMulti(initialData.processedData.sequenceNameToFastaId); const existingDataRows = Object.entries(initialData.originalData.unalignedNucleotideSequences).map( ([key, value]) => { const mapped = (fastaHeaderMap[key] ?? []).join(', ') || ''; diff --git a/website/src/components/ReviewPage/SequencesDialog.spec.tsx b/website/src/components/ReviewPage/SequencesDialog.spec.tsx index 98abed902a..eeb0c03ac7 100644 --- a/website/src/components/ReviewPage/SequencesDialog.spec.tsx +++ b/website/src/components/ReviewPage/SequencesDialog.spec.tsx @@ -93,7 +93,7 @@ const dataToView: SequenceEntryToEdit = { }, nucleotideInsertions: {}, aminoAcidInsertions: {}, - sequenceNameToFastaHeaderMap: { + sequenceNameToFastaId: { [sequence1]: 'header1', [sequence2]: 'header2', }, diff --git a/website/src/types/backend.ts b/website/src/types/backend.ts index 21310175e6..c7d73bcdf3 100644 --- a/website/src/types/backend.ts +++ b/website/src/types/backend.ts @@ -234,7 +234,7 @@ export const sequenceEntryToEdit = accessionVersion.merge( nucleotideInsertions: z.record(z.array(z.string())), alignedAminoAcidSequences: z.record(z.string().nullable()), aminoAcidInsertions: z.record(z.array(z.string())), - sequenceNameToFastaHeaderMap: z.record(z.string().nullable()), + sequenceNameToFastaId: z.record(z.string().nullable()), files: filesByCategory.nullable(), }), }), diff --git a/website/vitest.setup.ts b/website/vitest.setup.ts index ff28ccff4b..82659195a9 100755 --- a/website/vitest.setup.ts +++ b/website/vitest.setup.ts @@ -119,7 +119,7 @@ export const defaultReviewData: SequenceEntryToEdit = { aminoAcidInsertions: { processedInsertionGeneName: ['aminoAcidInsertion1', 'aminoAcidInsertion2'], }, - sequenceNameToFastaHeaderMap: { + sequenceNameToFastaId: { unalignedProcessedSequenceName: 'originalFastaHeader', }, files: null, From dd7e3042786431790f027076fdbecea728f41afa Mon Sep 17 00:00:00 2001 From: "Anna (Anya) Parker" <50943381+anna-parker@users.noreply.github.com> Date: Fri, 21 Nov 2025 16:56:23 +0100 Subject: [PATCH 17/44] make fasta id separator a constant (#5507) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolves # ### Screenshot ### PR Checklist - [ ] All necessary documentation has been adapted. - [ ] The implemented feature is covered by appropriate, automated tests. - [ ] Any manual testing that has been done is documented (i.e. what exactly was tested?) 🚀 Preview: Add `preview` label to enable --- .../backend/controller/SubmissionControllerDescriptions.kt | 3 ++- .../src/main/kotlin/org/loculus/backend/model/SubmitModel.kt | 1 + .../main/kotlin/org/loculus/backend/utils/MetadataEntry.kt | 3 ++- .../backend/controller/submission/ReviseEndpointTest.kt | 2 +- ingest/scripts/heuristic_group_segments.py | 4 +++- website/src/components/Edit/SequencesForm.tsx | 3 ++- website/src/types/config.ts | 2 ++ 7 files changed, 13 insertions(+), 5 deletions(-) diff --git a/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionControllerDescriptions.kt b/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionControllerDescriptions.kt index d33f65d399..fa4fb59521 100644 --- a/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionControllerDescriptions.kt +++ b/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionControllerDescriptions.kt @@ -1,6 +1,7 @@ package org.loculus.backend.controller import org.loculus.backend.model.FASTA_ID_HEADER +import org.loculus.backend.model.FASTA_ID_SEPARATOR import org.loculus.backend.model.METADATA_ID_HEADER const val SUBMIT_RESPONSE_DESCRIPTION = """ @@ -29,7 +30,7 @@ The file may be compressed with zstd, xz, zip, gzip, lzma, bzip2 (with common ex If the underlying organism has a single segment, the headers of the fasta file must match the '$METADATA_ID_HEADER' field in the metadata file. If the underlying organism has multiple segments, -the headers of the fasta file must be added in a space-separated list to the '$FASTA_ID_HEADER' +the headers of the fasta file must be added in a '$FASTA_ID_SEPARATOR'-separated list to the '$FASTA_ID_HEADER' field in the metadata file. """ diff --git a/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt b/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt index 2359e8b47f..c146b26a9c 100644 --- a/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt +++ b/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt @@ -34,6 +34,7 @@ import java.io.InputStream const val METADATA_ID_HEADER = "id" const val METADATA_ID_HEADER_ALTERNATE_FOR_BACKCOMPAT = "submissionId" const val FASTA_ID_HEADER = "fastaId" +const val FASTA_ID_SEPARATOR = " " const val ACCESSION_HEADER = "accession" private val log = KotlinLogging.logger { } diff --git a/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt b/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt index 46d0c66c6a..128841a60b 100644 --- a/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt +++ b/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt @@ -6,6 +6,7 @@ import org.apache.commons.csv.CSVRecord import org.loculus.backend.controller.UnprocessableEntityException import org.loculus.backend.model.ACCESSION_HEADER import org.loculus.backend.model.FASTA_ID_HEADER +import org.loculus.backend.model.FASTA_ID_SEPARATOR import org.loculus.backend.model.FastaId import org.loculus.backend.model.METADATA_ID_HEADER import org.loculus.backend.model.METADATA_ID_HEADER_ALTERNATE_FOR_BACKCOMPAT @@ -54,7 +55,7 @@ fun extractFastaIdsFromRecord(record: CSVRecord, submissionId: String, recordNum ) } - fastaIdValues.split(Regex(" ")) + fastaIdValues.split(Regex(FASTA_ID_SEPARATOR)) .map { it.trim() } .filter { it.isNotEmpty() } } diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/ReviseEndpointTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/ReviseEndpointTest.kt index 228a31af4b..61dbc1c8e5 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/ReviseEndpointTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/ReviseEndpointTest.kt @@ -131,7 +131,7 @@ class ReviseEndpointTest( } @Test - fun `GIVEN revision of multi-segmented entries with status 'APPROVED_FOR_RELEASE' THEN successful`() { + fun `WHEN submitting revision for multi-segmented entries with status 'APPROVED_FOR_RELEASE' THEN success`() { val accessions = convenienceClient.prepareDataTo(APPROVED_FOR_RELEASE).map { it.accession } client.reviseSequenceEntries( diff --git a/ingest/scripts/heuristic_group_segments.py b/ingest/scripts/heuristic_group_segments.py index eb14f46c2d..27d747af52 100644 --- a/ingest/scripts/heuristic_group_segments.py +++ b/ingest/scripts/heuristic_group_segments.py @@ -30,6 +30,8 @@ import orjsonl import yaml +FASTA_ID_SEPARATOR = " " + def sort_authors(authors: str) -> str: """Sort authors alphabetically""" @@ -211,7 +213,7 @@ def main( if segment in group ] ) - segments_list_str = " ".join([ + segments_list_str = FASTA_ID_SEPARATOR.join([ f"{joint_key}_{segment}" for segment in config.nucleotide_sequences if segment in group diff --git a/website/src/components/Edit/SequencesForm.tsx b/website/src/components/Edit/SequencesForm.tsx index 3799f25961..b568bd84bc 100644 --- a/website/src/components/Edit/SequencesForm.tsx +++ b/website/src/components/Edit/SequencesForm.tsx @@ -2,6 +2,7 @@ import { type Dispatch, type FC, type SetStateAction } from 'react'; import { toast } from 'react-toastify'; import { type SequenceEntryToEdit } from '../../types/backend.ts'; +import { FASTA_ID_SEPARATOR } from '../../types/config.ts'; import type { ReferenceGenomesLightweightSchema } from '../../types/referencesGenomes.ts'; import { FileUploadComponent } from '../Submission/FileUpload/FileUploadComponent.tsx'; import { PLAIN_SEGMENT_KIND, VirtualPlainSegmentFile } from '../Submission/FileUpload/fileProcessing.ts'; @@ -159,7 +160,7 @@ export class EditableSequences { getFastaIds(): string { const filledRows = this.rows.filter((row) => row.value !== null); - return filledRows.map((sequence) => sequence.fastaHeader).join(' '); + return filledRows.map((sequence) => sequence.fastaHeader).join(FASTA_ID_SEPARATOR); } getSequenceFasta(): File | undefined { diff --git a/website/src/types/config.ts b/website/src/types/config.ts index d006c42207..bfe7d9316b 100644 --- a/website/src/types/config.ts +++ b/website/src/types/config.ts @@ -3,6 +3,8 @@ import z from 'zod'; import { mutationProportionCount, orderDirection } from './lapis.ts'; import { referenceGenomes } from './referencesGenomes.ts'; +export const FASTA_ID_SEPARATOR = ' '; + // These metadata types need to be kept in sync with the backend config class `MetadataType` in Config.kt export const metadataPossibleTypes = z.enum([ 'string', From 50645b4005fccbf9adaacf269ec4a76c4574b73e Mon Sep 17 00:00:00 2001 From: anna-parker <50943381+anna-parker@users.noreply.github.com> Date: Mon, 24 Nov 2025 14:41:14 +0100 Subject: [PATCH 18/44] wupps --- preprocessing/nextclade/src/loculus_preprocessing/prepro.py | 1 + 1 file changed, 1 insertion(+) diff --git a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py index eb69ad4e57..fb1a42ece1 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py @@ -56,6 +56,7 @@ logger = logging.getLogger(__name__) + def accession_from_str(id_str: AccessionVersion) -> str: return id_str.split(".")[0] From d1f900ee3e262d3369937e8e92117298dc54e87b Mon Sep 17 00:00:00 2001 From: anna-parker <50943381+anna-parker@users.noreply.github.com> Date: Mon, 24 Nov 2025 17:38:43 +0100 Subject: [PATCH 19/44] fix integration tests --- integration-tests/tests/readonly.setup.ts | 59 +++++++++++++------ .../specs/features/revise-sequence.spec.ts | 34 +++++------ 2 files changed, 57 insertions(+), 36 deletions(-) diff --git a/integration-tests/tests/readonly.setup.ts b/integration-tests/tests/readonly.setup.ts index eb10a8520c..85cb3707e5 100644 --- a/integration-tests/tests/readonly.setup.ts +++ b/integration-tests/tests/readonly.setup.ts @@ -29,32 +29,53 @@ setup('Initialize some ebola sequences as base data', async ({ page }) => { } const submissionPage = new SingleSequenceSubmissionPage(page); - const reviewPage = await submissionPage.completeSubmission( + const mainSequence = + 'nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn' + + 'ATGGATAAACGGGTGAGAGGTTCATGGGCCCTGGGAGGACAATCTGAAGTTGATCTTGACTACCACAAAA' + + 'TATTAACAGCCGGGCTTTCGGTCCAACAAGGGATTGTGCGACAAAGAGTCATCCCGGTATATGTTGTGAG' + + 'TGATCTTGAGGGTATTTGTCAACATATCATTCAGGCCTTTGAAGCAGGCGTAGATTTCCAAGATAATGCT' + + 'GACAGCTTCCTTTTACTTTTATGTTTACATCATGCTTACCAAGGAGATCATAGGCTCTTCCTCAAAAGTG' + + 'ATGCAGTTCAATACTTAGAGGGCCATGGTTTCAGGTTTGAGGTCCGAGAAAAGGAGAATGTGCACCGTCT' + + 'GGATGAATTGTTGCCCAATGTCACCGGTGGAAAAAATCTTAGGAGAACATTGGCTGCAATGCCTGAAGAG' + + 'GAGACAACAGAAGCTAATGCTGGTCAGTTTTTATCCTTTGCCAGTTTGTTTCTACCCAAACTTGTCGTTG' + + 'GGGAGAAAGCGTGTCTGGAAAAAGTACAAAGGCAGATTCAGGTCCATGCAGAACAAGGGCTCATTCAATA' + + 'TCCAACTTCCTGGCAATCAGTTGGACACATGATGGTGATCTTCCGTTTGATGAGAACAAACTTTTTAATC' + + 'AAGTTCCTACTAATACATCAGGGGATGCACATGGTCGCAGGCCATGATGCGAATGACACAGTAATATCTA' + + 'ATTCTGTTGCCCAAGCAAGGTTCTCTGGTCTTCTGATTGTAAAGACTGTTCTGGACCACATCCTACAAAA' + + 'AACAGATCTTGGAGTACGACTTCATCCACTGGCCAGGACAGCAAAAGTCAAGAATGAGGTCAGTTCATTC' + + 'AAGGCAGCTCTTGGCTCACTTGCCAAGCATGGAGAATATGCTCCATTTGCACGTCTCCTCAATCTTTCTG'; + + const sequences = [ { - submissionId: 'foobar-readonly', + submissionId: 'foobar-readonly-1', collectionCountry: 'France', collectionDate: '2021-05-12', authorAffiliations: 'Patho Institute, Paris', - groupId: groupId.toString(), }, { - fastaHeader: - 'nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn' + - 'ATGGATAAACGGGTGAGAGGTTCATGGGCCCTGGGAGGACAATCTGAAGTTGATCTTGACTACCACAAAA' + - 'TATTAACAGCCGGGCTTTCGGTCCAACAAGGGATTGTGCGACAAAGAGTCATCCCGGTATATGTTGTGAG' + - 'TGATCTTGAGGGTATTTGTCAACATATCATTCAGGCCTTTGAAGCAGGCGTAGATTTCCAAGATAATGCT' + - 'GACAGCTTCCTTTTACTTTTATGTTTACATCATGCTTACCAAGGAGATCATAGGCTCTTCCTCAAAAGTG' + - 'ATGCAGTTCAATACTTAGAGGGCCATGGTTTCAGGTTTGAGGTCCGAGAAAAGGAGAATGTGCACCGTCT' + - 'GGATGAATTGTTGCCCAATGTCACCGGTGGAAAAAATCTTAGGAGAACATTGGCTGCAATGCCTGAAGAG' + - 'GAGACAACAGAAGCTAATGCTGGTCAGTTTTTATCCTTTGCCAGTTTGTTTCTACCCAAACTTGTCGTTG' + - 'GGGAGAAAGCGTGTCTGGAAAAAGTACAAAGGCAGATTCAGGTCCATGCAGAACAAGGGCTCATTCAATA' + - 'TCCAACTTCCTGGCAATCAGTTGGACACATGATGGTGATCTTCCGTTTGATGAGAACAAACTTTTTAATC' + - 'AAGTTCCTACTAATACATCAGGGGATGCACATGGTCGCAGGCCATGATGCGAATGACACAGTAATATCTA' + - 'ATTCTGTTGCCCAAGCAAGGTTCTCTGGTCTTCTGATTGTAAAGACTGTTCTGGACCACATCCTACAAAA' + - 'AACAGATCTTGGAGTACGACTTCATCCACTGGCCAGGACAGCAAAAGTCAAGAATGAGGTCAGTTCATTC' + - 'AAGGCAGCTCTTGGCTCACTTGCCAAGCATGGAGAATATGCTCCATTTGCACGTCTCCTCAATCTTTCTG', + submissionId: 'foobar-readonly-2', + collectionCountry: 'Brazil', + collectionDate: '2021-06-15', + authorAffiliations: 'Research Center, Rio', + }, + { + submissionId: 'foobar-readonly-3', + collectionCountry: 'Switzerland', + collectionDate: '2021-07-20', + authorAffiliations: 'University Hospital, Zurich', }, - ); + ]; + + for (const seq of sequences) { + const reviewPage = await submissionPage.completeSubmission( + { + ...seq, + groupId: groupId.toString(), + }, + { + fastaHeader: mainSequence, + }, + ); await reviewPage.waitForZeroProcessing(); await reviewPage.releaseValidSequences(); diff --git a/integration-tests/tests/specs/features/revise-sequence.spec.ts b/integration-tests/tests/specs/features/revise-sequence.spec.ts index cc8e2db1ff..7836ee8f67 100644 --- a/integration-tests/tests/specs/features/revise-sequence.spec.ts +++ b/integration-tests/tests/specs/features/revise-sequence.spec.ts @@ -39,15 +39,15 @@ sequenceTest( await page.getByRole('link', { name: 'Revise this sequence' }).click({ timeout: 15000 }); await expect(page.getByRole('heading', { name: 'Create new revision from' })).toBeVisible(); - await page.getByTestId(/^discard.*L\)_segment_file$/).click(); - await page.getByTestId(/^discard.*S\)_segment_file$/).click(); - const newSsequence = - 'CAAATGGTTTGAGGAGTTCAAGAAAGGAAATGGACTTGTGGACACTTTCACAAACTCNTATTCCTTTTGTGAAAGCGTNCCAAATCTGGACAGNTTTGTNTTCCAGATGGCNAGTGCCACTGATGATGCACAAAANGANTCCATCTACGCATCTGCNCTGGTGGANGCAACCAAATTTTGTGCACCTATATACGAGTGTGCTTGGGCTAGCTCCACTGGCATTGTTAAAAAGGGACTGGAGTGGTTCGAGAAAAATGCAGGAACCATTAAATCCTGGGATGAGAGTTATACTGAGCTTAAAGTTGAAGTTCCCAAAATAGAACAACTCTCCAACTACCAGCAGGCTGCTCTCAAATGGAGAAAAGACATAGGCTTCCGTGTCAATGCAAATACGGCAGCTTTGAGTAACAAAGTCCTAGCAGAGTACAAAGTTCCTGGCGAGATTGTAATGTCTGTCAAAGAGATGTTGTCAGATATGATTAGAAGNAGGAACCTGATTCTCAACAGAGGTGGTGATGAGAACCCACGCGGCCCAGTTAGCCGTGAACATGTGGAGTGGTGCAGGGAATTCGTCAAAGGCAAGTACATAATGGCTTTCAACCCACCCTGGGGAGACATCAACAAGTCAGGCCGTTCAGGAATAGCACTTGTTGCAACAGGCCTTGCCAAGCTTGCAGAGACTGAAGGGAAGGGAGTTTTTGACGAAGCCAAGAAGACTATAGAGGCTCTTAACGGGTATCTGGACAAGCATAAGGATGAAGTTGACAAAGCAAGTGCCGACAGCATGATAACAAACCTCCTTAAGCACATTGCTAAGGCACAAGAGCTTTACAAAAACTCGTCTGCTCTTCGTGCTCAGGGTGCACAGATTGACACCGTCTTCAGCTCATACTACTGGCTCTACAAGGCCGGTGTGACTCCAGAGACCTTCCCGACTGTTTCACAGTTCCTTTTTGAGTTAGGGAAGCAACCAAGGGGCACCAAGAAAATGAAGAAGGCACTCCTGAGCACCCCAATGAAGTGGGGAAAGAAGCTTTATGAGCTTTTTGCTGATGATTCCTTCCAACAGAACAGGATCTACATGCACCCCGCTGTGCTAACAGCTGGCAGAATCAGTGAAATGGGTGTCTGCTTCGGAACAATCCCTGTGGCCAATCCTGATGATGCCGCCTTAGGATCTGGACACACCAAGTCCATTCTCAACCTTCGGACAAACACTGAGACCAACAATCCGTGTGCCAAGACAATTGTTAAGTTGTTTGAAATTCANAAAACAGGGTTNAACATACAGGACATGGANATTGTGGCCTCNGAGCATCTGCTGCACCAATCCCTTGTTGGCAAGCAGTCTCCATTTCAAAATGCTTACAACGTCAAGGGGAANGCCACCAGTGCCAANATCATCTAAAGCNNANAATNNTCTNCAATCAGCTTTNCC'; - await page.getByTestId('Add a segment_segment_file').setInputFiles({ - name: 'update_S.txt', - mimeType: 'text/plain', - buffer: Buffer.from('>S\n' + newSsequence), - }); + await page.getByTestId(/^discard.*L\)_segment_file$/).click(); + await page.getByTestId(/^discard.*S\)_segment_file$/).click(); + const newSsequence = + 'CAAATGGTTTGAGGAGTTCAAGAAAGGAAATGGACTTGTGGACACTTTCACAAACTCNTATTCCTTTTGTGAAAGCGTNCCAAATCTGGACAGNTTTGTNTTCCAGATGGCNAGTGCCACTGATGATGCACAAAANGANTCCATCTACGCATCTGCNCTGGTGGANGCAACCAAATTTTGTGCACCTATATACGAGTGTGCTTGGGCTAGCTCCACTGGCATTGTTAAAAAGGGACTGGAGTGGTTCGAGAAAAATGCAGGAACCATTAAATCCTGGGATGAGAGTTATACTGAGCTTAAAGTTGAAGTTCCCAAAATAGAACAACTCTCCAACTACCAGCAGGCTGCTCTCAAATGGAGAAAAGACATAGGCTTCCGTGTCAATGCAAATACGGCAGCTTTGAGTAACAAAGTCCTAGCAGAGTACAAAGTTCCTGGCGAGATTGTAATGTCTGTCAAAGAGATGTTGTCAGATATGATTAGAAGNAGGAACCTGATTCTCAACAGAGGTGGTGATGAGAACCCACGCGGCCCAGTTAGCCGTGAACATGTGGAGTGGTGCAGGGAATTCGTCAAAGGCAAGTACATAATGGCTTTCAACCCACCCTGGGGAGACATCAACAAGTCAGGCCGTTCAGGAATAGCACTTGTTGCAACAGGCCTTGCCAAGCTTGCAGAGACTGAAGGGAAGGGAGTTTTTGACGAAGCCAAGAAGACTATAGAGGCTCTTAACGGGTATCTGGACAAGCATAAGGATGAAGTTGACAAAGCAAGTGCCGACAGCATGATAACAAACCTCCTTAAGCACATTGCTAAGGCACAAGAGCTTTACAAAAACTCGTCTGCTCTTCGTGCTCAGGGTGCACAGATTGACACCGTCTTCAGCTCATACTACTGGCTCTACAAGGCCGGTGTGACTCCAGAGACCTTCCCGACTGTTTCACAGTTCCTTTTTGAGTTAGGGAAGCAACCAAGGGGCACCAAGAAAATGAAGAAGGCACTCCTGAGCACCCCAATGAAGTGGGGAAAGAAGCTTTATGAGCTTTTTGCTGATGATTCCTTCCAACAGAACAGGATCTACATGCACCCCGCTGTGCTAACAGCTGGCAGAATCAGTGAAATGGGTGTCTGCTTCGGAACAATCCCTGTGGCCAATCCTGATGATGCCGCCTTAGGATCTGGACACACCAAGTCCATTCTCAACCTTCGGACAAACACTGAGACCAACAATCCGTGTGCCAAGACAATTGTTAAGTTGTTTGAAATTCANAAAACAGGGTTNAACATACAGGACATGGANATTGTGGCCTCNGAGCATCTGCTGCACCAATCCCTTGTTGGCAAGCAGTCTCCATTTCAAAATGCTTACAACGTCAAGGGGAANGCCACCAGTGCCAANATCATCTAAAGCNNANAATNNTCTNCAATCAGCTTTNCC'; + await page.getByTestId('Add a segment_segment_file').setInputFiles({ + name: 'update_S.txt', + mimeType: 'text/plain', + buffer: Buffer.from('>S\n' + newSsequence), + }); await page.getByRole('button', { name: 'Submit' }).click(); await page.getByRole('button', { name: 'Confirm' }).click(); @@ -56,15 +56,15 @@ sequenceTest( await reviewPage.waitForZeroProcessing(); await reviewPage.viewSequences(); - const tabs = await reviewPage.getAvailableSequenceTabs(); - expect(tabs).not.toContain('L (aligned)'); - expect(tabs).not.toContain('L (unaligned)'); + const tabs = await reviewPage.getAvailableSequenceTabs(); + expect(tabs).not.toContain('L (aligned)'); + expect(tabs).not.toContain('L (unaligned)'); - expect(tabs).toContain('S (unaligned)'); - await reviewPage.switchSequenceTab('S (unaligned)'); - const actual = (await reviewPage.getSequenceContent()).replace(/\s+/g, ''); - const expected = newSsequence.replace(/\s+/g, ''); - expect(actual.startsWith(expected)).toBe(true); + expect(tabs).toContain('S (unaligned)'); + await reviewPage.switchSequenceTab('S (unaligned)'); + const actual = (await reviewPage.getSequenceContent()).replace(/\s+/g, ''); + const expected = newSsequence.replace(/\s+/g, ''); + expect(actual.startsWith(expected)).toBe(true); await reviewPage.closeSequencesDialog(); }, From 190bbc0efd7bd3599c0007ef39a3bb5aaf584f7f Mon Sep 17 00:00:00 2001 From: anna-parker <50943381+anna-parker@users.noreply.github.com> Date: Tue, 25 Nov 2025 09:38:41 +0100 Subject: [PATCH 20/44] ingest from higher level EV taxon --- kubernetes/loculus/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kubernetes/loculus/values.yaml b/kubernetes/loculus/values.yaml index 337d7db59b..f103a92bc4 100644 --- a/kubernetes/loculus/values.yaml +++ b/kubernetes/loculus/values.yaml @@ -1880,7 +1880,7 @@ defaultOrganisms: ingest: <<: *ingest configFile: - taxon_id: 138948 # Enterovirus A (once we include non-A, broaden to 12059) + taxon_id: 12059 segment_identification: method: "minimizer" minimizer_index: "https://raw.githubusercontent.com/alejandra-gonzalezsanchez/loculus-evs/master/evs_minimizer-index.json" From 20731aad092d98b68eaa2b00098526b0d3b64f36 Mon Sep 17 00:00:00 2001 From: anna-parker <50943381+anna-parker@users.noreply.github.com> Date: Tue, 25 Nov 2025 14:16:50 +0100 Subject: [PATCH 21/44] merge conflict --- .../nextclade/tests/test_nextclade_preprocessing.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/preprocessing/nextclade/tests/test_nextclade_preprocessing.py b/preprocessing/nextclade/tests/test_nextclade_preprocessing.py index 99130ce646..0700828e5d 100644 --- a/preprocessing/nextclade/tests/test_nextclade_preprocessing.py +++ b/preprocessing/nextclade/tests/test_nextclade_preprocessing.py @@ -979,7 +979,7 @@ def test_create_flatfile(): "LEbolaZaire": ebola_zaire_aa(sequence_with_mutation("ebola-zaire"), "L"), }, aminoAcidInsertions={}, - sequenceNameToFastaHeaderMap={"ebola-zaire": "ebola-zaire"}, + sequenceNameToFastaId={"ebola-zaire": "ebola-zaire"}, ), ), Case( @@ -1009,7 +1009,7 @@ def test_create_flatfile(): "VP35EbolaSudan": ebola_sudan_aa(sequence_with_mutation("single"), "VP35"), }, aminoAcidInsertions={}, - sequenceNameToFastaHeaderMap={"ebola-sudan": "ebola-sudan"}, + sequenceNameToFastaId={"ebola-sudan": "ebola-sudan"}, ), ), Case( @@ -1073,7 +1073,7 @@ def test_create_flatfile(): "LEbolaZaire": ebola_zaire_aa(sequence_with_mutation("ebola-zaire"), "L"), }, aminoAcidInsertions={}, - sequenceNameToFastaHeaderMap={ + sequenceNameToFastaId={ "ebola-sudan": "ebola-sudan", "ebola-zaire": "ebola-zaire", }, From 0139905ec8b2f837342d9eef06c77c6912fae72f Mon Sep 17 00:00:00 2001 From: anna-parker <50943381+anna-parker@users.noreply.github.com> Date: Tue, 25 Nov 2025 14:11:26 +0100 Subject: [PATCH 22/44] feat(prepro): batch segment assignment --- kubernetes/loculus/values.yaml | 1 + .../src/loculus_preprocessing/config.py | 3 +- .../src/loculus_preprocessing/datatypes.py | 45 +- .../src/loculus_preprocessing/nextclade.py | 550 ++++++++++++------ .../src/loculus_preprocessing/prepro.py | 17 +- .../processing_functions.py | 3 +- .../nextclade/tests/multi_segment_config.yaml | 1 + .../tests/test_nextclade_preprocessing.py | 349 +++++++++-- 8 files changed, 726 insertions(+), 243 deletions(-) diff --git a/kubernetes/loculus/values.yaml b/kubernetes/loculus/values.yaml index f103a92bc4..147b1ba0ee 100644 --- a/kubernetes/loculus/values.yaml +++ b/kubernetes/loculus/values.yaml @@ -1861,6 +1861,7 @@ defaultOrganisms: - <<: *preprocessing configFile: <<: *preprocessingConfigFile + segment_classification_method: "minimizer" minimizer_index: "https://raw.githubusercontent.com/alejandra-gonzalezsanchez/loculus-evs/master/evs_minimizer-index.json" nucleotideSequences: - name: CV-A16 diff --git a/preprocessing/nextclade/src/loculus_preprocessing/config.py b/preprocessing/nextclade/src/loculus_preprocessing/config.py index 9c70d676d6..0cc30b06e4 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/config.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/config.py @@ -10,7 +10,7 @@ import yaml -from loculus_preprocessing.datatypes import MoleculeType, Topology +from loculus_preprocessing.datatypes import MoleculeType, SegmentClassificationMethod, Topology logger = logging.getLogger(__name__) @@ -73,6 +73,7 @@ class Config: multi_segment: bool = False alignment_requirement: AlignmentRequirement = AlignmentRequirement.ALL + segment_classification_method: SegmentClassificationMethod = SegmentClassificationMethod.ALIGN nextclade_dataset_server: str = "https://data.clades.nextstrain.org/v3" require_nextclade_sort_match: bool = False diff --git a/preprocessing/nextclade/src/loculus_preprocessing/datatypes.py b/preprocessing/nextclade/src/loculus_preprocessing/datatypes.py index 2424269a87..8c196cd9ca 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/datatypes.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/datatypes.py @@ -1,3 +1,4 @@ +from collections import defaultdict from collections.abc import Iterable from dataclasses import dataclass, field from enum import StrEnum, unique @@ -19,6 +20,7 @@ ProcessedMetadata = dict[str, ProcessedMetadataValue] InputMetadataValue = str | None InputMetadata = dict[str, InputMetadataValue] +FastaId = str ProcessingAnnotationAlignment: Final = "alignment" @@ -89,14 +91,6 @@ class UnprocessedEntry: FunctionArgs = dict[ArgName, ArgValue] -@dataclass -class SegmentAssignment: - unalignedNucleotideSequences: dict[SegmentName, NucleotideSequence | None] # noqa: N815 - sequenceNameToFastaId: dict[SegmentName, str] # noqa: N815 - errors: list[ProcessingAnnotation] - warnings: list[ProcessingAnnotation] - - @dataclass class ProcessingSpec: inputs: FunctionInputs @@ -116,7 +110,7 @@ class UnprocessedAfterNextclade: nucleotideInsertions: dict[SegmentName, list[NucleotideInsertion]] # noqa: N815 alignedAminoAcidSequences: dict[GeneName, AminoAcidSequence | None] # noqa: N815 aminoAcidInsertions: dict[GeneName, list[AminoAcidInsertion]] # noqa: N815 - sequenceNameToFastaId: dict[SegmentName, str] # noqa: N815 + sequenceNameToFastaId: dict[SegmentName, FastaId] # noqa: N815 errors: list[ProcessingAnnotation] warnings: list[ProcessingAnnotation] @@ -135,7 +129,7 @@ class ProcessedData: nucleotideInsertions: dict[SegmentName, Any] # noqa: N815 alignedAminoAcidSequences: dict[GeneName, Any] # noqa: N815 aminoAcidInsertions: dict[GeneName, Any] # noqa: N815 - sequenceNameToFastaId: dict[SegmentName, str] # noqa: N815 + sequenceNameToFastaId: dict[SegmentName, FastaId] # noqa: N815 files: dict[str, list[FileIdAndName]] | None = None @@ -146,8 +140,12 @@ class Annotation: @dataclass class Alerts: - errors: dict[AccessionVersion, list[ProcessingAnnotation]] = field(default_factory=dict) - warnings: dict[AccessionVersion, list[ProcessingAnnotation]] = field(default_factory=dict) + errors: dict[AccessionVersion, list[ProcessingAnnotation]] = field( + default_factory=lambda: defaultdict(list) + ) + warnings: dict[AccessionVersion, list[ProcessingAnnotation]] = field( + default_factory=lambda: defaultdict(list) + ) @dataclass @@ -185,6 +183,29 @@ class ProcessingResult: errors: list[ProcessingAnnotation] = field(default_factory=list) +@unique +class SegmentClassificationMethod(StrEnum): + ALIGN = "align" + MINIMIZER = "minimizer" + + +@dataclass +class SegmentAssignment: + unalignedNucleotideSequences: dict[SegmentName, NucleotideSequence | None] # noqa: N815 + sequenceNameToFastaId: dict[SegmentName, FastaId] # noqa: N815 + errors: list[ProcessingAnnotation] + warnings: list[ProcessingAnnotation] + + +@dataclass +class SegmentAssignmentBatch: + unalignedNucleotideSequences: dict[ + AccessionVersion, dict[SegmentName, NucleotideSequence | None] + ] + sequenceNameToFastaId: dict[AccessionVersion, dict[SegmentName, FastaId]] # noqa: N815 + alerts: Alerts + + @dataclass class FileUploadInfo: """Objects of this type are returned by the /files/request-upload endpoint.""" diff --git a/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py b/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py index 846cf11b7e..fa1db8daf2 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py @@ -21,6 +21,7 @@ AminoAcidInsertion, AminoAcidSequence, AnnotationSourceType, + FastaId, GeneName, GenericSequence, NucleotideInsertion, @@ -28,6 +29,8 @@ ProcessingAnnotation, ProcessingAnnotationAlignment, SegmentAssignment, + SegmentAssignmentBatch, + SegmentClassificationMethod, SegmentName, UnprocessedAfterNextclade, UnprocessedEntry, @@ -269,92 +272,219 @@ def check_nextclade_sort_matches( # noqa: PLR0913, PLR0917 return alerts -# TODO: running this for each sequence is inefficient, should be run once per batch +def assign_segment_with_nextclade_align( + unprocessed: Sequence[UnprocessedEntry], config: Config, dataset_dir: str +) -> SegmentAssignmentBatch: + """ + Run nextclade align + """ + unaligned_nucleotide_sequences: dict[ + AccessionVersion, dict[SegmentName, NucleotideSequence | None] + ] = defaultdict(dict) + sequenceNameToFastaId: dict[AccessionVersion, dict[SegmentName, str]] = defaultdict(dict) + alerts: Alerts = Alerts() + + has_missing_segments: dict[AccessionVersion, bool] = defaultdict(bool) + has_duplicate_segments: dict[AccessionVersion, bool] = defaultdict(bool) + + id_map: defaultdict[str, tuple[AccessionVersion, FastaId]] = defaultdict( + lambda: (AccessionVersion(), FastaId()) + ) + align_results_map: dict[AccessionVersion, dict[SegmentName, list[str]]] = defaultdict(dict) + input_unaligned_sequences: dict[ + AccessionVersion, dict[SegmentName, NucleotideSequence | None] + ] = defaultdict(dict) + + all_dfs = [] + with TemporaryDirectory(delete=not config.keep_tmp_dir) as result_dir: + input_file = result_dir + "/input.fasta" + os.makedirs(os.path.dirname(input_file), exist_ok=True) + for entry in unprocessed: + accession_version = entry.accessionVersion + input_unaligned_sequences[accession_version] = entry.data.unalignedNucleotideSequences + with open(input_file, "a", encoding="utf-8") as f: + for fastaId, seq in input_unaligned_sequences[accession_version].items(): + id = f"{accession_version}__{fastaId}" + id_map[id] = (accession_version, fastaId) + f.write(f">{id}\n") + f.write(f"{seq}\n") + + for sequence_and_dataset in config.nucleotideSequences: + segment = sequence_and_dataset.name + dataset_dir_seg = ( + dataset_dir if not config.multi_segment else dataset_dir + "/" + segment + ) + result_file_seg = result_dir + "/sort_output_" + segment + ".tsv" + + command = [ + "nextclade3", + "run", + f"--output-tsv={result_file_seg}", + f"--input-dataset={dataset_dir_seg}", + "--jobs=1", + "--", + input_file, + ] + exit_code = subprocess.run(command, check=False).returncode # noqa: S603 + if exit_code != 0: + msg = f"nextclade failed with exit code {exit_code}" + raise Exception(msg) + + logger.debug("Nextclade results available in %s", result_dir) + df = pd.read_csv(result_file_seg, sep="\t") + df["segment"] = segment + all_dfs.append(df) + + df_combined = pd.concat(all_dfs, ignore_index=True) + no_hits = df_combined[df_combined["alignmentScore"].isna()] + hits = ( + df_combined.dropna(subset=["alignmentScore"]) + .sort_values(by=["seqName", "alignmentScore"], ascending=[True, False]) + .drop_duplicates(subset="seqName", keep="first") + ) + for seq_name in no_hits["seqName"].unique(): + if seq_name not in hits["seqName"].unique(): + (accession_version, fastaId) = id_map[seq_name] + msg = ( + f"Sequence with fasta header {fastaId} does not align to any segment for" + f" organism: {config.organism} per `nextclade align`. " + f"Double check you are submitting to the correct organism." + ) + has_missing_segments[accession_version] = True + if config.alignment_requirement == AlignmentRequirement.ALL: + alerts.errors.setdefault(accession_version, []).append( + ProcessingAnnotation.from_single( + ProcessingAnnotationAlignment, + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + message=msg, + ) + ) + else: + alerts.warnings.setdefault(accession_version, []).append( + ProcessingAnnotation.from_single( + ProcessingAnnotationAlignment, + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + message=msg, + ) + ) + + best_hits = hits.groupby("seqName", as_index=False).first() + logger.debug(f"Found hits: {best_hits['seqName'].tolist()}") + + for _, row in best_hits.iterrows(): + (accession_version, fastaId) = id_map[row["seqName"]] + for seg in config.nucleotideSequences: + if row["segment"] == seg.name: + align_results_map[accession_version].setdefault(seg.name, []).append(fastaId) + break + continue + + for accession_version, segment_map in align_results_map.items(): + for segment_name, headers in segment_map.items(): + if len(headers) > 1: + msg = ( + f"Multiple sequences (with fasta headers: {', '.join(headers)}) align to " + f"{segment_name} - only one entry is allowed." + ) + has_duplicate_segments[accession_version] = True + alerts.errors.setdefault(accession_version, []).append( + ProcessingAnnotation.from_single( + ProcessingAnnotationAlignment, + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + message=msg, + ) + ) + continue + sequenceNameToFastaId[accession_version][segment_name] = headers[0] + unaligned_nucleotide_sequences[accession_version][segment_name] = ( + input_unaligned_sequences[accession_version][headers[0]] + ) + + for entry in unprocessed: + accession_version = entry.accessionVersion + if ( + len(unaligned_nucleotide_sequences[accession_version]) == 0 + and not has_duplicate_segments.get(accession_version) + and ( + not has_missing_segments.get(accession_version) + or config.alignment_requirement == AlignmentRequirement.ANY + ) + ): + alerts.errors.setdefault(accession_version, []).append( + ProcessingAnnotation.from_single( + ProcessingAnnotationAlignment, + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + message="No sequence data could be classified - " + "check you are submitting to the correct organism.", + ) + ) + + return SegmentAssignmentBatch( + unalignedNucleotideSequences=unaligned_nucleotide_sequences, + sequenceNameToFastaId=sequenceNameToFastaId, + alerts=alerts, + ) + + def assign_segment_with_nextclade_sort( - input_unaligned_sequences: dict[str, NucleotideSequence | None], - config: Config, - dataset_dir: str, -) -> SegmentAssignment: + unprocessed: Sequence[UnprocessedEntry], config: Config, dataset_dir: str +) -> SegmentAssignmentBatch: """ Run nextclade sort - assert highest score is in sequence_and_dataset.accepted_sort_matches (default is nextclade_dataset_name) """ - errors = [] - warnings = [] - unaligned_nucleotide_sequences: dict[SegmentName, NucleotideSequence | None] = {} - nextclade_dataset_server = config.nextclade_dataset_server - has_duplicate_segments = False - has_missing_segments = False + unaligned_nucleotide_sequences: dict[ + AccessionVersion, dict[SegmentName, NucleotideSequence | None] + ] = defaultdict(dict) + sequenceNameToFastaId: dict[AccessionVersion, dict[SegmentName, str]] = defaultdict(dict) + alerts: Alerts = Alerts() + has_missing_segments: dict[AccessionVersion, bool] = defaultdict(bool) + has_duplicate_segments: dict[AccessionVersion, bool] = defaultdict(bool) + + id_map: defaultdict[str, tuple[AccessionVersion, FastaId]] = defaultdict( + lambda: (AccessionVersion(), FastaId()) + ) + sort_results_map: dict[AccessionVersion, dict[SegmentName, list[str]]] = defaultdict(dict) + input_unaligned_sequences: dict[ + AccessionVersion, dict[SegmentName, NucleotideSequence | None] + ] = defaultdict(dict) with TemporaryDirectory(delete=not config.keep_tmp_dir) as result_dir: input_file = result_dir + "/input.fasta" os.makedirs(os.path.dirname(input_file), exist_ok=True) - with open(input_file, "w", encoding="utf-8") as f: - for id, seq in input_unaligned_sequences.items(): - f.write(f">{id}\n") - f.write(f"{seq}\n") + for entry in unprocessed: + accession_version = entry.accessionVersion + input_unaligned_sequences[accession_version] = entry.data.unalignedNucleotideSequences + with open(input_file, "a", encoding="utf-8") as f: + for fastaId, seq in input_unaligned_sequences[accession_version].items(): + id = f"{accession_version}__{fastaId}" + id_map[id] = (accession_version, fastaId) + f.write(f">{id}\n") + f.write(f"{seq}\n") result_file = result_dir + "/sort_output.tsv" df = run_sort( result_file, input_file, config, - nextclade_dataset_server, + config.nextclade_dataset_server, dataset_dir, ) - no_hits = df[df["score"].isna()] - hits = df.dropna(subset=["score"]).sort_values("score", ascending=False) - for seq_name in no_hits["seqName"].unique(): - if seq_name not in hits["seqName"].unique(): - msg = ( - f"Sequence with fasta header {seq_name} does not appear to match any reference for organism: " - f"{config.organism} per `nextclade sort`. " - f"Double check you are submitting to the correct organism." - ) - has_missing_segments = True - if config.alignment_requirement == AlignmentRequirement.ALL: - errors.append( - ProcessingAnnotation.from_single( - ProcessingAnnotationAlignment, - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - message=msg, - ) - ) - else: - warnings.append( - ProcessingAnnotation.from_single( - ProcessingAnnotationAlignment, - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - message=msg, - ) - ) - - best_hits = hits.groupby("seqName", as_index=False).first() - logger.info(f"Found hits: {best_hits['seqName'].tolist()}") - - sort_results_map: dict[SegmentName, list[str]] = {} - - for _, row in best_hits.iterrows(): - not_found = True - for segment in config.nucleotideSequences: - # TODO: need to check somewhere that accepted_sort_matches does not overlap across segments - if row["dataset"] in segment.accepted_sort_matches: - not_found = False - sort_results_map.setdefault(segment.name, []).append(row["seqName"]) - break - if not not_found: - continue - has_missing_segments = True + no_hits = df[df["score"].isna()] + hits = df.dropna(subset=["score"]).sort_values("score", ascending=False) + for seq_name in no_hits["seqName"].unique(): + if seq_name not in hits["seqName"].unique(): + (accession_version, fastaId) = id_map[seq_name] msg = ( - f"Sequence {row['seqName']} best matches {row['dataset']}, " - "which is currently not an accepted option for organism: " - f"{config.organism}. It is therefore not possible to release. " - "Contact the administrator if you think this message is an error." + f"Sequence with fasta header {fastaId} does not appear to match any reference for" + f" organism: {config.organism} per `nextclade sort`. " + f"Double check you are submitting to the correct organism." ) + has_missing_segments[accession_version] = True if config.alignment_requirement == AlignmentRequirement.ALL: - errors.append( + alerts.errors.setdefault(accession_version, []).append( ProcessingAnnotation.from_single( ProcessingAnnotationAlignment, AnnotationSourceType.NUCLEOTIDE_SEQUENCE, @@ -362,22 +492,61 @@ def assign_segment_with_nextclade_sort( ) ) else: - warnings.append( + alerts.warnings.setdefault(accession_version, []).append( ProcessingAnnotation.from_single( ProcessingAnnotationAlignment, AnnotationSourceType.NUCLEOTIDE_SEQUENCE, message=msg, ) ) - sequenceNameToFastaId: dict[SegmentName, str] = {} - for segment_name, headers in sort_results_map.items(): + + best_hits = hits.groupby("seqName", as_index=False).first() + logger.debug(f"Found hits: {best_hits['seqName'].tolist()}") + + for _, row in best_hits.iterrows(): + (accession_version, fastaId) = id_map[row["seqName"]] + not_found = True + for segment in config.nucleotideSequences: + # TODO: need to check that accepted_sort_matches does not overlap across segments + if row["dataset"] in segment.accepted_sort_matches: + not_found = False + sort_results_map[accession_version].setdefault(segment.name, []).append(fastaId) + break + if not not_found: + continue + has_missing_segments[accession_version] = True + msg = ( + f"Sequence {fastaId} best matches {row['dataset']}, " + "which is currently not an accepted option for organism: " + f"{config.organism}. It is therefore not possible to release. " + "Contact the administrator if you think this message is an error." + ) + if config.alignment_requirement == AlignmentRequirement.ALL: + alerts.errors[accession_version].append( + ProcessingAnnotation.from_single( + ProcessingAnnotationAlignment, + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + message=msg, + ) + ) + else: + alerts.warnings[accession_version].append( + ProcessingAnnotation.from_single( + ProcessingAnnotationAlignment, + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + message=msg, + ) + ) + + for accession_version, segment_map in sort_results_map.items(): + for segment_name, headers in segment_map.items(): if len(headers) > 1: msg = ( f"Multiple sequences (with fasta headers: {', '.join(headers)}) align to " - f" {segment_name} - only one entry is allowed." + f"{segment_name} - only one entry is allowed." ) - has_duplicate_segments = True - errors.append( + has_duplicate_segments[accession_version] = True + alerts.errors.setdefault(accession_version, []).append( ProcessingAnnotation.from_single( ProcessingAnnotationAlignment, AnnotationSourceType.NUCLEOTIDE_SEQUENCE, @@ -385,27 +554,34 @@ def assign_segment_with_nextclade_sort( ) ) continue - sequenceNameToFastaId[segment_name] = headers[0] - unaligned_nucleotide_sequences[segment_name] = input_unaligned_sequences[headers[0]] - - if ( - len(unaligned_nucleotide_sequences) == 0 - and not has_duplicate_segments - and (not has_missing_segments or config.alignment_requirement == AlignmentRequirement.ANY) - ): - errors.append( - ProcessingAnnotation.from_single( - ProcessingAnnotationAlignment, - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - message="No sequence data could be classified - check you are submitting to the correct organism.", + sequenceNameToFastaId[accession_version][segment_name] = headers[0] + unaligned_nucleotide_sequences[accession_version][segment_name] = ( + input_unaligned_sequences[accession_version][headers[0]] ) - ) - return SegmentAssignment( + for entry in unprocessed: + accession_version = entry.accessionVersion + if ( + len(unaligned_nucleotide_sequences[accession_version]) == 0 + and not has_duplicate_segments.get(accession_version) + and ( + not has_missing_segments.get(accession_version) + or config.alignment_requirement == AlignmentRequirement.ANY + ) + ): + alerts.errors.setdefault(accession_version, []).append( + ProcessingAnnotation.from_single( + ProcessingAnnotationAlignment, + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + message="No sequence data could be classified - " + "check you are submitting to the correct organism.", + ) + ) + + return SegmentAssignmentBatch( unalignedNucleotideSequences=unaligned_nucleotide_sequences, sequenceNameToFastaId=sequenceNameToFastaId, - errors=errors, - warnings=warnings, + alerts=alerts, ) @@ -443,6 +619,33 @@ def assign_single_segment( ) +def assign_all_single_segments( + unprocessed: Sequence[UnprocessedEntry], config: Config +) -> SegmentAssignmentBatch: + unaligned_nucleotide_sequences: dict[ + AccessionVersion, dict[SegmentName, NucleotideSequence | None] + ] = defaultdict(dict) + segment_to_header: dict[AccessionVersion, dict[SegmentName, str]] = defaultdict(dict) + alerts: Alerts = Alerts() + for entry in unprocessed: + accession_version = entry.accessionVersion + segment_assignment = assign_single_segment( + entry.data.unalignedNucleotideSequences, + config=config, + ) + segment_to_header[accession_version] = segment_assignment.sequenceNameToFastaId + unaligned_nucleotide_sequences[accession_version] = ( + segment_assignment.unalignedNucleotideSequences + ) + alerts.errors[accession_version] = segment_assignment.errors + alerts.warnings[accession_version] = segment_assignment.warnings + return SegmentAssignmentBatch( + unalignedNucleotideSequences=unaligned_nucleotide_sequences, + sequenceNameToFastaId=segment_to_header, + alerts=alerts, + ) + + def assign_segment_using_header( input_unaligned_sequences: dict[str, NucleotideSequence | None], config: Config, @@ -504,7 +707,7 @@ def assign_segment_using_header( f"{', '.join(remaining_segments)}. " "Each metadata entry can have multiple corresponding fasta sequence " "entries with format _ valid segments are: " - f"{', '.join([sequence_and_dataset.name for sequence_and_dataset in config.nucleotideSequences])}." + f"{', '.join([seq.name for seq in config.nucleotideSequences])}." ), ) ) @@ -524,7 +727,53 @@ def assign_segment_using_header( ) -def enrich_with_nextclade( # noqa: C901, PLR0914, PLR0915 +def load_aligned_nuc_sequences( + result_dir_seg: str, + segment: SegmentName, + aligned_nucleotide_sequences: dict[ + AccessionVersion, dict[SegmentName, NucleotideSequence | None] + ], +) -> dict[AccessionVersion, dict[SegmentName, NucleotideSequence | None]]: + """ + Load the nextclade alignment results into the aligned_nucleotide_sequences dict, mapping each + accession to a segmentName: NucleotideSequence dictionary. + """ + with open(result_dir_seg + "/nextclade.aligned.fasta", encoding="utf-8") as aligned_nucs: + aligned_nuc = SeqIO.parse(aligned_nucs, "fasta") + for aligned_sequence in aligned_nuc: + sequence_id: str = aligned_sequence.id + sequence: NucleotideSequence = str(aligned_sequence.seq) + aligned_nucleotide_sequences[sequence_id][segment] = mask_terminal_gaps(sequence) + return aligned_nucleotide_sequences + + +def load_aligned_aa_sequences( + result_dir_seg: str, + config: Config, + aligned_aminoacid_sequences: dict[AccessionVersion, dict[GeneName, AminoAcidSequence | None]], +) -> dict[AccessionVersion, dict[GeneName, AminoAcidSequence | None]]: + """ + Load the nextclade amino acid alignment results into the aligned_aminoacid_sequences dict, mapping each + accession to a geneName: AminoAcidSequence dictionary. + """ + for gene in config.genes: + translation_path = result_dir_seg + f"/nextclade.cds_translation.{gene}.fasta" + try: + with open(translation_path, encoding="utf-8") as aligned_translations: + aligned_translation = SeqIO.parse(aligned_translations, "fasta") + for aligned_sequence in aligned_translation: + sequence_id = aligned_sequence.id + masked_sequence = mask_terminal_gaps(str(aligned_sequence.seq), mask_char="X") + aligned_aminoacid_sequences[sequence_id][gene] = masked_sequence + except FileNotFoundError: + # This can happen if the sequence does not cover this gene + logger.debug( + f"Gene {gene} not found in Nextclade results expected at: {translation_path}" + ) + return aligned_aminoacid_sequences + + +def enrich_with_nextclade( # noqa: PLR0914 unprocessed: Sequence[UnprocessedEntry], dataset_dir: str, config: Config ) -> dict[AccessionVersion, UnprocessedAfterNextclade]: """ @@ -541,43 +790,45 @@ def enrich_with_nextclade( # noqa: C901, PLR0914, PLR0915 sequenceNameToFastaId: dict[SegmentName, str] )` object. """ - unaligned_nucleotide_sequences: dict[ - AccessionVersion, dict[SegmentName, NucleotideSequence | None] - ] = {} - segment_assignment_map: dict[AccessionVersion, dict[SegmentName, str]] = {} - alerts: Alerts = Alerts() - input_metadata: dict[AccessionVersion, dict[str, Any]] = {} - aligned_aminoacid_sequences: dict[ - AccessionVersion, dict[GeneName, AminoAcidSequence | None] - ] = {} - aligned_nucleotide_sequences: dict[ - AccessionVersion, dict[SegmentName, NucleotideSequence | None] - ] = {} - for entry in unprocessed: - id = entry.accessionVersion - input_metadata[id] = entry.data.metadata - input_metadata[id]["submitter"] = entry.data.submitter - input_metadata[id]["submittedAt"] = entry.data.submittedAt - input_metadata[id]["group_id"] = entry.data.group_id - aligned_aminoacid_sequences[id] = {} - aligned_nucleotide_sequences[id] = {} - segment_assignment_map[id] = {} - if not config.multi_segment: - segment_assignment = assign_single_segment( - input_unaligned_sequences=entry.data.unalignedNucleotideSequences, + input_metadata: dict[AccessionVersion, dict[str, Any]] = { + entry.accessionVersion: { + **entry.data.metadata, + "submitter": entry.data.submitter, + "submittedAt": entry.data.submittedAt, + "group_id": entry.data.group_id, + } + for entry in unprocessed + } + + if not config.multi_segment: + batch = assign_all_single_segments( + unprocessed, + config=config, + ) + else: + batch = ( + assign_segment_with_nextclade_sort( + unprocessed, config=config, + dataset_dir=dataset_dir, ) - else: - segment_assignment = assign_segment_with_nextclade_sort( - input_unaligned_sequences=entry.data.unalignedNucleotideSequences, + if config.segment_classification_method == SegmentClassificationMethod.MINIMIZER + else assign_segment_with_nextclade_align( + unprocessed, config=config, dataset_dir=dataset_dir, ) - unaligned_nucleotide_sequences[id] = segment_assignment.unalignedNucleotideSequences - alerts.errors[id] = segment_assignment.errors - alerts.warnings[id] = segment_assignment.warnings - segment_assignment_map[id] = segment_assignment.sequenceNameToFastaId + ) + unaligned_nucleotide_sequences = batch.unalignedNucleotideSequences + segment_assignment_map = batch.sequenceNameToFastaId + alerts: Alerts = batch.alerts + aligned_nucleotide_sequences: dict[ + AccessionVersion, dict[SegmentName, NucleotideSequence | None] + ] = defaultdict(dict) + aligned_aminoacid_sequences: dict[ + AccessionVersion, dict[GeneName, AminoAcidSequence | None] + ] = defaultdict(dict) nextclade_metadata: defaultdict[ AccessionVersion, defaultdict[SegmentName, dict[str, Any] | None] ] = defaultdict(lambda: defaultdict(dict)) @@ -587,7 +838,7 @@ def enrich_with_nextclade( # noqa: C901, PLR0914, PLR0915 amino_acid_insertions: defaultdict[ AccessionVersion, defaultdict[GeneName, list[AminoAcidInsertion]] ] = defaultdict(lambda: defaultdict(list)) - with TemporaryDirectory(delete=not config.keep_tmp_dir) as result_dir: # noqa: PLR1702 + with TemporaryDirectory(delete=not config.keep_tmp_dir) as result_dir: for sequence_and_dataset in config.nucleotideSequences: segment = sequence_and_dataset.name result_dir_seg = result_dir if not config.multi_segment else result_dir + "/" + segment @@ -608,7 +859,12 @@ def enrich_with_nextclade( # noqa: C901, PLR0914, PLR0915 if config.require_nextclade_sort_match: alerts = check_nextclade_sort_matches( - result_dir_seg, input_file, alerts, config, sequence_and_dataset, dataset_dir + result_dir_seg, + input_file, + alerts, + config, + sequence_and_dataset, + dataset_dir, ) command = [ @@ -636,31 +892,9 @@ def enrich_with_nextclade( # noqa: C901, PLR0914, PLR0915 aligned_nucleotide_sequences = load_aligned_nuc_sequences( result_dir_seg, segment, aligned_nucleotide_sequences ) - - for gene in config.genes: - translation_path = result_dir_seg + f"/nextclade.cds_translation.{gene}.fasta" - try: - with open(translation_path, encoding="utf-8") as aligned_translations: - aligned_translation = SeqIO.parse(aligned_translations, "fasta") - for aligned_sequence in aligned_translation: - sequence_id = aligned_sequence.id - masked_sequence = mask_terminal_gaps( - str(aligned_sequence.seq), mask_char="X" - ) - gene_name = ( - sequence_and_dataset.gene_prefix + gene - if sequence_and_dataset.gene_prefix - else gene - ) - aligned_aminoacid_sequences[sequence_id][gene_name] = masked_sequence - except FileNotFoundError: - # TODO: Add warning to each sequence - logger.info( - f"Gene {gene} not found in Nextclade results expected at: { - translation_path - }" - ) - + aligned_aminoacid_sequences = load_aligned_aa_sequences( + result_dir_seg, config, aligned_aminoacid_sequences + ) nextclade_metadata = parse_nextclade_json( result_dir_seg, nextclade_metadata, segment, unaligned_nucleotide_sequences ) # this includes the "annotation" field @@ -685,30 +919,10 @@ def enrich_with_nextclade( # noqa: C901, PLR0914, PLR0915 errors=alerts.errors[id], warnings=alerts.warnings[id], ) - for id in unaligned_nucleotide_sequences + for id in input_metadata } -def load_aligned_nuc_sequences( - result_dir_seg: str, - segment: SegmentName, - aligned_nucleotide_sequences: dict[ - AccessionVersion, dict[SegmentName, NucleotideSequence | None] - ], -) -> dict[AccessionVersion, dict[SegmentName, NucleotideSequence | None]]: - """ - Load the nextclade alignment results into the aligned_nucleotide_sequences dict, mapping each - accession to a segmentName: NucleotideSequence dictionary. - """ - with open(result_dir_seg + "/nextclade.aligned.fasta", encoding="utf-8") as aligned_nucs: - aligned_nuc = SeqIO.parse(aligned_nucs, "fasta") - for aligned_sequence in aligned_nuc: - sequence_id: str = aligned_sequence.id - sequence: NucleotideSequence = str(aligned_sequence.seq) - aligned_nucleotide_sequences[sequence_id][segment] = mask_terminal_gaps(sequence) - return aligned_nucleotide_sequences - - def download_nextclade_dataset(dataset_dir: str, config: Config) -> None: for sequence_and_dataset in config.nucleotideSequences: name = sequence_and_dataset.name diff --git a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py index fb1a42ece1..acd3442d1c 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py @@ -261,26 +261,27 @@ def add_alignment_errors_warnings( return (errors, warnings) aligned_segments = set() for segment in config.nucleotideSequences: - if segment not in unprocessed.unalignedNucleotideSequences: + segment_name = segment.name + if segment_name not in unprocessed.unalignedNucleotideSequences: continue if unprocessed.nextcladeMetadata and ( - segment not in unprocessed.nextcladeMetadata - or (unprocessed.nextcladeMetadata[segment] is None) + segment_name not in unprocessed.nextcladeMetadata + or (unprocessed.nextcladeMetadata[segment_name] is None) ): message = ( "Nucleotide sequence failed to align" if not config.multi_segment - else f"Nucleotide sequence for {segment} failed to align" + else f"Nucleotide sequence for {segment_name} failed to align" ) annotation = ProcessingAnnotation.from_single( - segment, AnnotationSourceType.NUCLEOTIDE_SEQUENCE, message=message + segment_name, AnnotationSourceType.NUCLEOTIDE_SEQUENCE, message=message ) if config.multi_segment and config.alignment_requirement == AlignmentRequirement.ANY: warnings.append(annotation) else: errors.append(annotation) continue - aligned_segments.add(segment) + aligned_segments.add(segment_name) if ( not aligned_segments @@ -332,7 +333,9 @@ def get_output_metadata( ) continue - if output_field.startswith("length_") and output_field[7:] in [seq.name for seq in config.nucleotideSequences]: + if output_field.startswith("length_") and output_field[7:] in [ + seq.name for seq in config.nucleotideSequences + ]: segment = output_field[7:] output_metadata[output_field] = get_sequence_length( unprocessed.unalignedNucleotideSequences, segment diff --git a/preprocessing/nextclade/src/loculus_preprocessing/processing_functions.py b/preprocessing/nextclade/src/loculus_preprocessing/processing_functions.py index c5f18511cf..e462624e4a 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/processing_functions.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/processing_functions.py @@ -103,7 +103,8 @@ def check_latin_characters( ) -> tuple[list[ProcessingAnnotation], list[ProcessingAnnotation]]: warnings: list[ProcessingAnnotation] = [] errors: list[ProcessingAnnotation] = [] - # Check if all characters in the authors string are Latin letters or spaces (transformable to ASCII) + # Check if all characters in the authors string are Latin letters or spaces + # (transformable to ASCII) for char in authors: # If character is already ASCII, skip if ord(char) < 128: diff --git a/preprocessing/nextclade/tests/multi_segment_config.yaml b/preprocessing/nextclade/tests/multi_segment_config.yaml index 5a8367e462..b888b0e483 100644 --- a/preprocessing/nextclade/tests/multi_segment_config.yaml +++ b/preprocessing/nextclade/tests/multi_segment_config.yaml @@ -7,6 +7,7 @@ genes: - LEbolaZaire log_level: DEBUG minimizer_index: TEST +segment_classification_method: "minimizer" nextclade_dataset_server: TEST nucleotideSequences: - name: ebola-sudan diff --git a/preprocessing/nextclade/tests/test_nextclade_preprocessing.py b/preprocessing/nextclade/tests/test_nextclade_preprocessing.py index 0700828e5d..cc6a3a6ed5 100644 --- a/preprocessing/nextclade/tests/test_nextclade_preprocessing.py +++ b/preprocessing/nextclade/tests/test_nextclade_preprocessing.py @@ -24,6 +24,7 @@ AnnotationSourceType, ProcessingAnnotation, ProcessingAnnotationAlignment, + SegmentClassificationMethod, SubmissionData, UnprocessedData, UnprocessedEntry, @@ -420,7 +421,7 @@ def invalid_sequence() -> str: ), ] -multi_segment_case_definitions_all_requirement = [ +multi_segment_case_definitions_all_requirement_align_classification = [ Case( name="with one failed alignment, one not uploaded", input_metadata={}, @@ -441,7 +442,9 @@ def invalid_sequence() -> str: ProcessingAnnotationHelper( ["alignment"], ["alignment"], - "Sequence with fasta header fastaHeader1 does not appear to match any reference for organism: multi-ebola-test per `nextclade sort`. Double check you are submitting to the correct organism.", + "Sequence with fasta header fastaHeader1 does not align to any segment for " + "organism: multi-ebola-test per `nextclade align`. " + "Double check you are submitting to the correct organism.", AnnotationSourceType.NUCLEOTIDE_SEQUENCE, ) ] @@ -479,7 +482,9 @@ def invalid_sequence() -> str: ProcessingAnnotationHelper( ["alignment"], ["alignment"], - "Sequence with fasta header fastaHeader1 does not appear to match any reference for organism: multi-ebola-test per `nextclade sort`. Double check you are submitting to the correct organism.", + "Sequence with fasta header fastaHeader1 does not align to any segment for " + "organism: multi-ebola-test per `nextclade align`. " + "Double check you are submitting to the correct organism.", AnnotationSourceType.NUCLEOTIDE_SEQUENCE, ), ] @@ -503,7 +508,191 @@ def invalid_sequence() -> str: ), ] -multi_segment_case_definitions_any_requirement = [ +multi_segment_case_definitions_all_requirement_sort_classification = [ + Case( + name="with one failed alignment, one not uploaded", + input_metadata={}, + input_sequence={"fastaHeader1": invalid_sequence()}, + accession_id="1", + expected_metadata={ + "totalInsertedNucs_ebola-sudan": None, + "totalSnps_ebola-sudan": None, + "totalDeletedNucs_ebola-sudan": None, + "length_ebola-sudan": 0, + "totalInsertedNucs_ebola-zaire": None, + "totalSnps_ebola-zaire": None, + "totalDeletedNucs_ebola-zaire": None, + "length_ebola-zaire": 0, + }, + expected_errors=build_processing_annotations( + [ + ProcessingAnnotationHelper( + ["alignment"], + ["alignment"], + "Sequence with fasta header fastaHeader1 does not appear to match any reference" + " for organism: multi-ebola-test per `nextclade sort`. " + "Double check you are submitting to the correct organism.", + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + ) + ] + ), + expected_warnings=[], + expected_processed_alignment=ProcessedAlignment( + unalignedNucleotideSequences={}, + alignedNucleotideSequences={}, + nucleotideInsertions={}, + alignedAminoAcidSequences={}, + aminoAcidInsertions={}, + sequenceNameToFastaId={}, + ), + ), + Case( + name="with one failed alignment, one succeeded", + input_metadata={}, + input_sequence={ + "fastaHeader1": invalid_sequence(), + "fastaHeader2": sequence_with_mutation("ebola-zaire"), + }, + accession_id="1", + expected_metadata={ + "totalInsertedNucs_ebola-sudan": None, + "totalSnps_ebola-sudan": None, + "totalDeletedNucs_ebola-sudan": None, + "length_ebola-sudan": 0, + "totalInsertedNucs_ebola-zaire": 0, + "totalSnps_ebola-zaire": 1, + "totalDeletedNucs_ebola-zaire": 0, + "length_ebola-zaire": len(consensus_sequence("ebola-zaire")), + }, + expected_errors=build_processing_annotations( + [ + ProcessingAnnotationHelper( + ["alignment"], + ["alignment"], + "Sequence with fasta header fastaHeader1 does not appear to match any reference" + " for organism: multi-ebola-test per `nextclade sort`. " + "Double check you are submitting to the correct organism.", + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + ), + ] + ), + expected_warnings=[], + expected_processed_alignment=ProcessedAlignment( + unalignedNucleotideSequences={ + "ebola-zaire": sequence_with_mutation("ebola-zaire"), + }, + alignedNucleotideSequences={ + "ebola-zaire": sequence_with_mutation("ebola-zaire"), + }, + nucleotideInsertions={}, + alignedAminoAcidSequences={ + "VP24EbolaZaire": ebola_zaire_aa(sequence_with_mutation("ebola-zaire"), "VP24"), + "LEbolaZaire": ebola_zaire_aa(sequence_with_mutation("ebola-zaire"), "L"), + }, + aminoAcidInsertions={}, + sequenceNameToFastaId={"ebola-zaire": "fastaHeader2"}, + ), + ), +] + +multi_segment_case_definitions_any_requirement_sort_classification = [ + Case( + name="with one failed alignment, one not uploaded", + input_metadata={}, + input_sequence={"fastaHeader1": invalid_sequence()}, + accession_id="1", + expected_metadata={ + "totalInsertedNucs_ebola-sudan": None, + "totalSnps_ebola-sudan": None, + "totalDeletedNucs_ebola-sudan": None, + "length_ebola-sudan": 0, + "totalInsertedNucs_ebola-zaire": None, + "totalSnps_ebola-zaire": None, + "totalDeletedNucs_ebola-zaire": None, + "length_ebola-zaire": 0, + }, + expected_errors=build_processing_annotations( + [ + ProcessingAnnotationHelper( + [ProcessingAnnotationAlignment], + [ProcessingAnnotationAlignment], + "No sequence data could be classified - " + "check you are submitting to the correct organism.", + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + ) + ] + ), + expected_warnings=build_processing_annotations( + [ + ProcessingAnnotationHelper( + ["alignment"], + ["alignment"], + "Sequence with fasta header fastaHeader1 does not appear to match any reference " + "for organism: multi-ebola-test per `nextclade sort`. " + "Double check you are submitting to the correct organism.", + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + ) + ] + ), + expected_processed_alignment=ProcessedAlignment( + unalignedNucleotideSequences={}, + alignedNucleotideSequences={}, + nucleotideInsertions={}, + alignedAminoAcidSequences={}, + aminoAcidInsertions={}, + sequenceNameToFastaId={}, + ), + ), + Case( + name="with one failed alignment, one succeeded", + input_metadata={}, + input_sequence={ + "fastaHeader1": invalid_sequence(), + "fastaHeader2": sequence_with_mutation("ebola-zaire"), + }, + accession_id="1", + expected_metadata={ + "totalInsertedNucs_ebola-sudan": None, + "totalSnps_ebola-sudan": None, + "totalDeletedNucs_ebola-sudan": None, + "length_ebola-sudan": 0, + "totalInsertedNucs_ebola-zaire": 0, + "totalSnps_ebola-zaire": 1, + "totalDeletedNucs_ebola-zaire": 0, + "length_ebola-zaire": len(consensus_sequence("ebola-zaire")), + }, + expected_errors=[], + expected_warnings=build_processing_annotations( + [ + ProcessingAnnotationHelper( + ["alignment"], + ["alignment"], + "Sequence with fasta header fastaHeader1 does not appear to match any reference" + " for organism: multi-ebola-test per `nextclade sort`. " + "Double check you are submitting to the correct organism.", + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + ) + ] + ), + expected_processed_alignment=ProcessedAlignment( + unalignedNucleotideSequences={ + "ebola-zaire": sequence_with_mutation("ebola-zaire"), + }, + alignedNucleotideSequences={ + "ebola-zaire": sequence_with_mutation("ebola-zaire"), + }, + nucleotideInsertions={}, + alignedAminoAcidSequences={ + "VP24EbolaZaire": ebola_zaire_aa(sequence_with_mutation("ebola-zaire"), "VP24"), + "LEbolaZaire": ebola_zaire_aa(sequence_with_mutation("ebola-zaire"), "L"), + }, + aminoAcidInsertions={}, + sequenceNameToFastaId={"ebola-zaire": "fastaHeader2"}, + ), + ), +] + +multi_segment_case_definitions_any_requirement_align_classification = [ Case( name="with one failed alignment, one not uploaded", input_metadata={}, @@ -524,7 +713,8 @@ def invalid_sequence() -> str: ProcessingAnnotationHelper( [ProcessingAnnotationAlignment], [ProcessingAnnotationAlignment], - "No sequence data could be classified - check you are submitting to the correct organism.", + "No sequence data could be classified - " + "check you are submitting to the correct organism.", AnnotationSourceType.NUCLEOTIDE_SEQUENCE, ) ] @@ -534,7 +724,9 @@ def invalid_sequence() -> str: ProcessingAnnotationHelper( ["alignment"], ["alignment"], - "Sequence with fasta header fastaHeader1 does not appear to match any reference for organism: multi-ebola-test per `nextclade sort`. Double check you are submitting to the correct organism.", + "Sequence with fasta header fastaHeader1 does not align to any segment for " + "organism: multi-ebola-test per `nextclade align`. " + "Double check you are submitting to the correct organism.", AnnotationSourceType.NUCLEOTIDE_SEQUENCE, ) ] @@ -572,7 +764,9 @@ def invalid_sequence() -> str: ProcessingAnnotationHelper( ["alignment"], ["alignment"], - "Sequence with fasta header fastaHeader1 does not appear to match any reference for organism: multi-ebola-test per `nextclade sort`. Double check you are submitting to the correct organism.", + "Sequence with fasta header fastaHeader1 does not align to any segment for " + "organism: multi-ebola-test per `nextclade align`. " + "Double check you are submitting to the correct organism.", AnnotationSourceType.NUCLEOTIDE_SEQUENCE, ) ] @@ -595,6 +789,7 @@ def invalid_sequence() -> str: ), ] + segment_validation_tests_single_segment = [ Case( name="do not accept multiple segments for single segment", @@ -605,18 +800,20 @@ def invalid_sequence() -> str: }, accession_id="2", expected_metadata={"length": 0}, - expected_errors=build_processing_annotations([ - ProcessingAnnotationHelper( - [ProcessingAnnotationAlignment], - [ProcessingAnnotationAlignment], - "Multiple sequences: ['fastaHeader1', 'fastaHeader2'] found in the" - " input data, but organism: ebola-sudan-test is single-segmented. " - "Please check that your metadata and sequences are annotated correctly." - "Each metadata entry should have a single corresponding fasta sequence " - "entry with the same submissionId.", - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - ), - ]), + expected_errors=build_processing_annotations( + [ + ProcessingAnnotationHelper( + [ProcessingAnnotationAlignment], + [ProcessingAnnotationAlignment], + "Multiple sequences: ['fastaHeader1', 'fastaHeader2'] found in the" + " input data, but organism: ebola-sudan-test is single-segmented. " + "Please check that your metadata and sequences are annotated correctly." + "Each metadata entry should have a single corresponding fasta sequence " + "entry with the same submissionId.", + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + ), + ] + ), expected_warnings=[], expected_processed_alignment=ProcessedAlignment( unalignedNucleotideSequences={}, @@ -648,14 +845,17 @@ def invalid_sequence() -> str: "totalDeletedNucs_ebola-zaire": None, "length_ebola-zaire": 0, }, - expected_errors=build_processing_annotations([ - ProcessingAnnotationHelper( - [ProcessingAnnotationAlignment], - [ProcessingAnnotationAlignment], - "Multiple sequences (with fasta headers: duplicate_ebola-sudan, ebola-sudan) align to ebola-sudan - only one entry is allowed.", - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - ), - ]), + expected_errors=build_processing_annotations( + [ + ProcessingAnnotationHelper( + [ProcessingAnnotationAlignment], + [ProcessingAnnotationAlignment], + "Multiple sequences (with fasta headers: duplicate_ebola-sudan, ebola-sudan) " + "align to ebola-sudan - only one entry is allowed.", + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + ), + ] + ), expected_warnings=[], expected_processed_alignment=ProcessedAlignment( unalignedNucleotideSequences={}, @@ -709,16 +909,18 @@ def invalid_sequence() -> str: "length_ebola-sudan": 0, "length_ebola-zaire": 0, }, - expected_errors=build_processing_annotations([ - ProcessingAnnotationHelper( - [ProcessingAnnotationAlignment], - [ProcessingAnnotationAlignment], - "Found multiple sequences with the same segment name: ebola-sudan. " - "Each metadata entry can have multiple corresponding fasta sequence " - "entries with format _.", - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - ) - ]), + expected_errors=build_processing_annotations( + [ + ProcessingAnnotationHelper( + [ProcessingAnnotationAlignment], + [ProcessingAnnotationAlignment], + "Found multiple sequences with the same segment name: ebola-sudan. " + "Each metadata entry can have multiple corresponding fasta sequence " + "entries with format _.", + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + ) + ] + ), expected_warnings=[], expected_processed_alignment=ProcessedAlignment( unalignedNucleotideSequences={}, @@ -741,17 +943,19 @@ def invalid_sequence() -> str: "length_ebola-sudan": len(consensus_sequence("ebola-sudan")), "length_ebola-zaire": 0, }, - expected_errors=build_processing_annotations([ - ProcessingAnnotationHelper( - [ProcessingAnnotationAlignment], - [ProcessingAnnotationAlignment], - "Found sequences in the input data with segments that are not in the config: " - "unknown_segment. Each metadata entry can have multiple corresponding fasta sequence " - "entries with format _ valid segments are: " - "ebola-sudan, ebola-zaire.", - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - ) - ]), + expected_errors=build_processing_annotations( + [ + ProcessingAnnotationHelper( + [ProcessingAnnotationAlignment], + [ProcessingAnnotationAlignment], + "Found sequences in the input data with segments that are not in the config: " + "unknown_segment. Each metadata entry can have multiple corresponding fasta " + "sequence entries with format _ valid segments are: " + "ebola-sudan, ebola-zaire.", + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + ) + ] + ), expected_warnings=[], expected_processed_alignment=ProcessedAlignment( unalignedNucleotideSequences={"ebola-sudan": sequence_with_mutation("ebola-sudan")}, @@ -791,10 +995,10 @@ def test_preprocessing_single_segment(test_case_def: Case): "test_case_def", multi_segment_case_definitions + segment_validation_tests_multi_segments - + multi_segment_case_definitions_all_requirement, - ids=lambda tc: f"multi segment {tc.name}", + + multi_segment_case_definitions_all_requirement_sort_classification, + ids=lambda tc: f"multi segment, segment classification with sort {tc.name}", ) -def test_preprocessing_multi_segment_all_requirement(test_case_def: Case): +def test_preprocessing_multi_segment_all_requirement_sort_classification(test_case_def: Case): config = get_config(MULTI_SEGMENT_CONFIG, ignore_args=True) factory_custom = ProcessedEntryFactory(all_metadata_fields=list(config.processing_spec.keys())) test_case = test_case_def.create_test_case(factory_custom) @@ -808,12 +1012,49 @@ def test_preprocessing_multi_segment_all_requirement(test_case_def: Case): "test_case_def", multi_segment_case_definitions + segment_validation_tests_multi_segments - + multi_segment_case_definitions_any_requirement, - ids=lambda tc: f"multi segment {tc.name}", + + multi_segment_case_definitions_all_requirement_align_classification, + ids=lambda tc: f"multi segment, segment classification with align {tc.name}", +) +def test_preprocessing_multi_segment_all_requirement_align_classification(test_case_def: Case): + config = get_config(MULTI_SEGMENT_CONFIG, ignore_args=True) + config.segment_classification_method = SegmentClassificationMethod.ALIGN + factory_custom = ProcessedEntryFactory(all_metadata_fields=list(config.processing_spec.keys())) + test_case = test_case_def.create_test_case(factory_custom) + processed_entry = process_single_entry(test_case, config, MULTI_EBOLA_DATASET) + verify_processed_entry( + processed_entry.processed_entry, test_case.expected_output, test_case.name + ) + + +@pytest.mark.parametrize( + "test_case_def", + multi_segment_case_definitions + + segment_validation_tests_multi_segments + + multi_segment_case_definitions_any_requirement_sort_classification, + ids=lambda tc: f"multi segment, segment classification with sort {tc.name}", +) +def test_preprocessing_multi_segment_any_requirement_sort_classification(test_case_def: Case): + config = get_config(MULTI_SEGMENT_CONFIG, ignore_args=True) + config.alignment_requirement = AlignmentRequirement.ANY + factory_custom = ProcessedEntryFactory(all_metadata_fields=list(config.processing_spec.keys())) + test_case = test_case_def.create_test_case(factory_custom) + processed_entry = process_single_entry(test_case, config, MULTI_EBOLA_DATASET) + verify_processed_entry( + processed_entry.processed_entry, test_case.expected_output, test_case.name + ) + + +@pytest.mark.parametrize( + "test_case_def", + multi_segment_case_definitions + + segment_validation_tests_multi_segments + + multi_segment_case_definitions_any_requirement_align_classification, + ids=lambda tc: f"multi segment, segment classification with align {tc.name}", ) -def test_preprocessing_multi_segment_any_requirement(test_case_def: Case): +def test_preprocessing_multi_segment_any_requirement_align_classification(test_case_def: Case): config = get_config(MULTI_SEGMENT_CONFIG, ignore_args=True) config.alignment_requirement = AlignmentRequirement.ANY + config.segment_classification_method = SegmentClassificationMethod.ALIGN factory_custom = ProcessedEntryFactory(all_metadata_fields=list(config.processing_spec.keys())) test_case = test_case_def.create_test_case(factory_custom) processed_entry = process_single_entry(test_case, config, MULTI_EBOLA_DATASET) From ae18b3b4fee61c1d0c91781a7d6cc1dc8b5e57d8 Mon Sep 17 00:00:00 2001 From: "Anna (Anya) Parker" <50943381+anna-parker@users.noreply.github.com> Date: Thu, 27 Nov 2025 15:35:01 +0100 Subject: [PATCH 23/44] Assign genotype in prepro (#5551) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolves https://github.com/loculus-project/loculus/issues/5511 - [x] Add a test - [x] deduplicate code - [x] make genotype a required field? 🚀 Preview: https://add-genotype.loculus.org --- .../loculus/templates/ingest-config.yaml | 4 +- kubernetes/loculus/values.yaml | 8 +--- .../src/loculus_preprocessing/prepro.py | 45 +++++++++++++++++++ .../tests/multi_pathogen_config.yaml | 7 +++ .../tests/test_nextclade_preprocessing.py | 41 ++++++++--------- 5 files changed, 75 insertions(+), 30 deletions(-) diff --git a/kubernetes/loculus/templates/ingest-config.yaml b/kubernetes/loculus/templates/ingest-config.yaml index f3ded64e31..378ebd5c05 100644 --- a/kubernetes/loculus/templates/ingest-config.yaml +++ b/kubernetes/loculus/templates/ingest-config.yaml @@ -6,7 +6,7 @@ {{- range $key, $values := (include "loculus.enabledOrganisms" . | fromJson) }} {{- if $values.ingest }} {{- $metadata := (include "loculus.patchMetadataSchema" $values.schema | fromYaml).metadata }} -{{- $nucleotideSequencesList := (include "loculus.patchMetadataSchema" $values.schema | fromYaml).nucleotideSequences | default (list "main")}} +{{- $rawUniqueSegments := (include "loculus.extractUniqueRawNucleotideSequenceNames" .referenceGenomes | fromYaml).segments }} --- apiVersion: v1 kind: ConfigMap @@ -15,7 +15,7 @@ metadata: data: config.yaml: | {{- $values.ingest.configFile | toYaml | nindent 4 }} - nucleotide_sequences: {{- $nucleotideSequencesList | toYaml | nindent 4 }} + nucleotide_sequences: {{- $rawUniqueSegments | toYaml | nindent 4 }} verify_loculus_version_is: {{$dockerTag}} check_ena_deposition: {{ not $.Values.disableEnaSubmission }} {{- if not $.Values.disableEnaSubmission }} diff --git a/kubernetes/loculus/values.yaml b/kubernetes/loculus/values.yaml index 147b1ba0ee..6f0b04f099 100644 --- a/kubernetes/loculus/values.yaml +++ b/kubernetes/loculus/values.yaml @@ -1841,8 +1841,9 @@ defaultOrganisms: noInput: true generateIndex: true autocomplete: true - ingest: segment initiallyVisible: true + preprocessing: + inputs: {input: ASSIGNED_SEGMENT} website: <<: *website tableColumns: @@ -1882,11 +1883,6 @@ defaultOrganisms: <<: *ingest configFile: taxon_id: 12059 - segment_identification: - method: "minimizer" - minimizer_index: "https://raw.githubusercontent.com/alejandra-gonzalezsanchez/loculus-evs/master/evs_minimizer-index.json" - minimizer_parser: - - segment referenceGenomes: CV-A16: nucleotideSequences: diff --git a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py index acd3442d1c..d418d76ee7 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py @@ -151,6 +151,49 @@ def add_nextclade_metadata( return InputData(datum=str(raw)) +def add_assigned_segment( + unprocessed: UnprocessedAfterNextclade, + config: Config, +) -> InputData: + if not unprocessed.nextcladeMetadata: + return InputData(datum=None) + valid_segments = [key for key, value in unprocessed.nextcladeMetadata.items() if value] + if not valid_segments: + return InputData(datum=None) + if len(valid_segments) > 1: + return InputData( + datum=None, + errors=[ + MultipleValidSegmentsError(valid_segments).getProcessingAnnotation( + processed_field_name="ASSIGNED_SEGMENT", organism=config.organism + ) + ], + ) + return InputData(datum=valid_segments[0]) + + + +def add_assigned_segment( + unprocessed: UnprocessedAfterNextclade, + config: Config, +) -> InputData: + if not unprocessed.nextcladeMetadata: + return InputData(datum=None) + valid_segments = [key for key, value in unprocessed.nextcladeMetadata.items() if value] + if not valid_segments: + return InputData(datum=None) + if len(valid_segments) > 1: + return InputData( + datum=None, + errors=[ + MultipleValidSegmentsError(valid_segments).getProcessingAnnotation( + processed_field_name="ASSIGNED_SEGMENT", organism=config.organism + ) + ], + ) + return InputData(datum=valid_segments[0]) + + def add_input_metadata( spec: ProcessingSpec, unprocessed: UnprocessedAfterNextclade, @@ -159,6 +202,8 @@ def add_input_metadata( ) -> InputData: """Returns value of input_path in unprocessed metadata""" # If field starts with "nextclade.", take from nextclade metadata + if input_path == "ASSIGNED_SEGMENT": + return add_assigned_segment(unprocessed, config=config) nextclade_prefix = "nextclade." if input_path.startswith(nextclade_prefix): nextclade_path = input_path[len(nextclade_prefix) :] diff --git a/preprocessing/nextclade/tests/multi_pathogen_config.yaml b/preprocessing/nextclade/tests/multi_pathogen_config.yaml index 9a8a6f13e5..6713d15e04 100644 --- a/preprocessing/nextclade/tests/multi_pathogen_config.yaml +++ b/preprocessing/nextclade/tests/multi_pathogen_config.yaml @@ -18,6 +18,13 @@ nucleotideSequences: accepted_sort_matches: ebola-zaire organism: multi-ebola-test processing_spec: + subtype: + args: + type: str + function: identity + inputs: + input: ASSIGNED_SEGMENT + required: true totalSnps: args: useFirstSegment: true diff --git a/preprocessing/nextclade/tests/test_nextclade_preprocessing.py b/preprocessing/nextclade/tests/test_nextclade_preprocessing.py index cc6a3a6ed5..ab916112c4 100644 --- a/preprocessing/nextclade/tests/test_nextclade_preprocessing.py +++ b/preprocessing/nextclade/tests/test_nextclade_preprocessing.py @@ -1192,6 +1192,17 @@ def test_create_flatfile(): assert embl_str == expected_embl +def multiple_valid_segments_error(metadata_name: str) -> ProcessingAnnotation: + return ProcessingAnnotation( + unprocessedFields=[ + AnnotationSource(name="ebola-sudan", type=AnnotationSourceType.NUCLEOTIDE_SEQUENCE), + AnnotationSource(name="ebola-zaire", type=AnnotationSourceType.NUCLEOTIDE_SEQUENCE), + ], + processedFields=[AnnotationSource(name=metadata_name, type=AnnotationSourceType.METADATA)], + message="Organism multi-ebola-test is configured to only accept one segment per submission, found multiple valid segments: ['ebola-sudan', 'ebola-zaire'].", + ) + + multi_pathogen_cases = [ Case( name="with only first one uploaded", @@ -1204,6 +1215,7 @@ def test_create_flatfile(): "totalInsertedNucs": 0, "totalSnps": 1, "length": len(consensus_sequence("ebola-zaire")), + "subtype": "ebola-zaire", }, expected_errors=[], expected_warnings=[], @@ -1234,6 +1246,7 @@ def test_create_flatfile(): "totalInsertedNucs": 0, "totalSnps": 1, "length": len(consensus_sequence("ebola-sudan")), + "subtype": "ebola-sudan", }, expected_errors=[], expected_warnings=[], @@ -1269,32 +1282,16 @@ def test_create_flatfile(): expected_errors=[ ProcessingAnnotation( unprocessedFields=[ - AnnotationSource( - name="ebola-sudan", type=AnnotationSourceType.NUCLEOTIDE_SEQUENCE - ), - AnnotationSource( - name="ebola-zaire", type=AnnotationSourceType.NUCLEOTIDE_SEQUENCE - ), - ], - processedFields=[ - AnnotationSource(name="totalInsertions", type=AnnotationSourceType.METADATA) - ], - message="Organism multi-ebola-test is configured to only accept one segment per submission, found multiple valid segments: ['ebola-sudan', 'ebola-zaire'].", - ), - ProcessingAnnotation( - unprocessedFields=[ - AnnotationSource( - name="ebola-sudan", type=AnnotationSourceType.NUCLEOTIDE_SEQUENCE - ), - AnnotationSource( - name="ebola-zaire", type=AnnotationSourceType.NUCLEOTIDE_SEQUENCE - ), + AnnotationSource(name="ASSIGNED_SEGMENT", type=AnnotationSourceType.METADATA) ], processedFields=[ - AnnotationSource(name="totalSubstitutions", type=AnnotationSourceType.METADATA) + AnnotationSource(name="subtype", type=AnnotationSourceType.METADATA) ], - message="Organism multi-ebola-test is configured to only accept one segment per submission, found multiple valid segments: ['ebola-sudan', 'ebola-zaire'].", + message="Metadata field subtype is required.", ), + multiple_valid_segments_error(metadata_name="ASSIGNED_SEGMENT"), + multiple_valid_segments_error(metadata_name="totalInsertions"), + multiple_valid_segments_error(metadata_name="totalSubstitutions"), ], expected_warnings=[], expected_processed_alignment=ProcessedAlignment( From 470b78ba7d6f61df31adb8afb8c1936a63f1d4d6 Mon Sep 17 00:00:00 2001 From: Cornelius Roemer Date: Thu, 27 Nov 2025 16:04:03 +0100 Subject: [PATCH 24/44] refactorings of prepro multi-segment (#5552) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🚀 Preview: https://cr-refactorings.loculus.org --------- Co-authored-by: anna-parker <50943381+anna-parker@users.noreply.github.com> --- .../src/loculus_preprocessing/backend.py | 17 +- .../src/loculus_preprocessing/config.py | 14 - .../src/loculus_preprocessing/nextclade.py | 441 +++++++----------- .../src/loculus_preprocessing/prepro.py | 9 +- .../ebola-dataset/ebola-sudan/.gitignore | 1 + .../nextclade/tests/factory_methods.py | 10 + .../nextclade/tests/multi_segment_config.yaml | 1 - .../tests/test_nextclade_preprocessing.py | 109 ++--- 8 files changed, 248 insertions(+), 354 deletions(-) create mode 100644 preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/.gitignore diff --git a/preprocessing/nextclade/src/loculus_preprocessing/backend.py b/preprocessing/nextclade/src/loculus_preprocessing/backend.py index 8fc48fbde1..01010045d2 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/backend.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/backend.py @@ -221,17 +221,24 @@ def upload_embl_file_to_presigned_url(content: str, url: str) -> None: raise RuntimeError(msg) -def download_minimizer(url, save_path): +def download_minimizer(config, save_path): + if config.minimizer_index: + url = config.minimizer_index + elif config.nextclade_dataset_server: + url = config.nextclade_dataset_server.rstrip("/") + "/minimizer_index.json" + else: + msg = "Cannot download minimizer: no minimizer_index or nextclade_dataset_server specified in config" + logger.error(msg) + raise RuntimeError(msg) + try: response = requests.get(url, timeout=10) response.raise_for_status() - Path(save_path).parent.mkdir(parents=True, exist_ok=True) Path(save_path).write_bytes(response.content) - - logger.info(f"Minimizer downloaded successfully and saved to '{save_path}'") - except requests.exceptions.RequestException as e: msg = f"Failed to download minimizer: {e}" logger.error(msg) raise RuntimeError(msg) from e + + logger.info(f"Minimizer downloaded successfully and saved to '{save_path}'") diff --git a/preprocessing/nextclade/src/loculus_preprocessing/config.py b/preprocessing/nextclade/src/loculus_preprocessing/config.py index 0cc30b06e4..02c160784a 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/config.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/config.py @@ -90,19 +90,6 @@ class Config: ) -def get_accepted_sort_matches( - segment: NextcladeSequenceAndDataset, -) -> list[str]: - accepted_dataset_names = set() - - if segment.accepted_sort_matches: - accepted_dataset_names.update(segment.accepted_sort_matches) - if segment.nextclade_dataset_name: - accepted_dataset_names.add(segment.nextclade_dataset_name) - accepted_dataset_names.add(segment.name) - return list(accepted_dataset_names) - - def assign_nextclade_sequence_and_dataset( nuc_seq_values: list[dict[str, Any]], ) -> list[NextcladeSequenceAndDataset]: @@ -118,7 +105,6 @@ def assign_nextclade_sequence_and_dataset( for seq_key, seq_value in value.items(): if hasattr(seq_and_dataset, seq_key) and seq_value is not None: setattr(seq_and_dataset, seq_key, seq_value) - seq_and_dataset.accepted_sort_matches = get_accepted_sort_matches(seq_and_dataset) nextclade_sequence_and_dataset_list.append(seq_and_dataset) return nextclade_sequence_and_dataset_list diff --git a/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py b/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py index fa1db8daf2..f0e36a1661 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py @@ -42,6 +42,16 @@ logger = logging.getLogger(__name__) +def sequence_annotation( + message: str, +) -> ProcessingAnnotation: + return ProcessingAnnotation.from_single( + ProcessingAnnotationAlignment, + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + message=message, + ) + + def mask_terminal_gaps( sequence: GenericSequence, mask_char: Literal["N"] | Literal["X"] = "N" ) -> GenericSequence: @@ -147,38 +157,32 @@ def parse_nextclade_json( def run_sort( result_file: str, input_file: str, - config: Config, - nextclade_dataset_server: str, dataset_dir: str, ) -> pd.DataFrame: """ Run nextclade - use config.minimizer_index or default minimizer from nextclade server """ - if config.minimizer_index: - minimizer_file = dataset_dir + "/minimizer/minimizer.json" - - test = nextclade_dataset_server == "TEST" - - subprocess_args_with_emtpy_strings = [ - "nextclade3", - "sort", - input_file, - "-m" if config.minimizer_index else "", - f"{minimizer_file}" if config.minimizer_index else "", - "--output-results-tsv", - f"{result_file}", - "--max-score-gap", - "0.3", - "--min-score", - "0.05", - "--min-hits", - "2", - "--all-matches", - "--server" if not test else "", - f"{nextclade_dataset_server}" if not test else "", + subprocess_args = [ + arg + for arg in [ + "nextclade3", + "sort", + f"-m={dataset_dir}/minimizer/minimizer.json", + "--output-results-tsv", + result_file, + "--max-score-gap", + "0.3", + "--min-score", + "0.05", + "--min-hits", + "2", + "--all-matches", + "--", + input_file, + ] + if arg ] - subprocess_args = [arg for arg in subprocess_args_with_emtpy_strings if arg] logger.debug(f"Running nextclade sort: {subprocess_args}") @@ -198,12 +202,25 @@ def run_sort( ) +def accepted_sort_matches_or_default( + segment: NextcladeSequenceAndDataset, +) -> list[str]: + accepted_dataset_names = set() + + if segment.accepted_sort_matches: + accepted_dataset_names.update(segment.accepted_sort_matches) + if segment.nextclade_dataset_name: + accepted_dataset_names.add(segment.nextclade_dataset_name) + accepted_dataset_names.add(segment.name) + return list(accepted_dataset_names) + + def check_nextclade_sort_matches( # noqa: PLR0913, PLR0917 result_file_dir: str, input_file: str, alerts: Alerts, - config: Config, - sequence_and_dataset: NextcladeSequenceAndDataset, + organism: str, + accepted_sort_matches: list[str], dataset_dir: str, ) -> Alerts: """ @@ -211,26 +228,10 @@ def check_nextclade_sort_matches( # noqa: PLR0913, PLR0917 - assert highest score is in sequence_and_dataset.accepted_sort_matches (default is nextclade_dataset_name) """ - nextclade_dataset_name = sequence_and_dataset.nextclade_dataset_name - if not sequence_and_dataset.accepted_sort_matches and not nextclade_dataset_name: - logger.warning("No nextclade dataset name or accepted dataset match list found in config") - return alerts - nextclade_dataset_server = ( - sequence_and_dataset.nextclade_dataset_server or config.nextclade_dataset_server - ) - - accepted_dataset_names = ( - sequence_and_dataset.accepted_sort_matches - or [nextclade_dataset_name] # type: ignore - or [sequence_and_dataset.name] # type: ignore - ) - result_file = result_file_dir + "/sort_output.tsv" df = run_sort( result_file, input_file, - config, - nextclade_dataset_server, dataset_dir, ) @@ -243,35 +244,55 @@ def check_nextclade_sort_matches( # noqa: PLR0913, PLR0917 for seq in missing_ids: alerts.warnings[seq].append( - ProcessingAnnotation.from_single( - ProcessingAnnotationAlignment, - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - message=( - "Sequence does not appear to match reference, per `nextclade sort`. " - "Double check you are submitting to the correct organism." - ), + sequence_annotation( + "Sequence does not appear to match reference, per `nextclade sort`. " + "Double check you are submitting to the correct organism." ) ) for _, row in best_hits.iterrows(): # If best match is not the same as the dataset we are submitting to, add an error - if row["dataset"] not in accepted_dataset_names: + if row["dataset"] not in accepted_sort_matches: alerts.errors[row["seqName"]].append( - ProcessingAnnotation.from_single( - ProcessingAnnotationAlignment, - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - message=( - f"Sequence best matches {row['dataset']}, " - "a different organism than the one you are submitting to: " - f"{config.organism}. It is therefore not possible to release. " - "Contact the administrator if you think this message is an error." - ), - ) + sequence_annotation( + f"Sequence best matches {row['dataset']}, " + "a different organism than the one you are submitting to: " + f"{organism}. It is therefore not possible to release. " + "Contact the administrator if you think this message is an error." + ), ) return alerts +def write_nextclade_input_fasta( + unprocessed: Sequence[UnprocessedEntry], input_file: str +) -> tuple[ + dict[AccessionVersion, dict[SegmentName, NucleotideSequence | None]], + defaultdict[str, tuple[AccessionVersion, FastaId]], +]: + """ + Write unprocessed sequences to a fasta file for nextclade input + """ + input_unaligned_sequences: dict[ + AccessionVersion, dict[SegmentName, NucleotideSequence | None] + ] = defaultdict(dict) + id_map: defaultdict[str, tuple[AccessionVersion, FastaId]] = defaultdict( + lambda: (AccessionVersion(), FastaId()) + ) + os.makedirs(os.path.dirname(input_file), exist_ok=True) + with open(input_file, "w", encoding="utf-8") as f: + for entry in unprocessed: + accession_version = entry.accessionVersion + input_unaligned_sequences[accession_version] = entry.data.unalignedNucleotideSequences + for fasta_id, seq in input_unaligned_sequences[accession_version].items(): + id = f"{accession_version}__{fasta_id}" + id_map[id] = (accession_version, fasta_id) + f.write(f">{id}\n") + f.write(f"{seq}\n") + return input_unaligned_sequences, id_map + + def assign_segment_with_nextclade_align( unprocessed: Sequence[UnprocessedEntry], config: Config, dataset_dir: str ) -> SegmentAssignmentBatch: @@ -287,9 +308,6 @@ def assign_segment_with_nextclade_align( has_missing_segments: dict[AccessionVersion, bool] = defaultdict(bool) has_duplicate_segments: dict[AccessionVersion, bool] = defaultdict(bool) - id_map: defaultdict[str, tuple[AccessionVersion, FastaId]] = defaultdict( - lambda: (AccessionVersion(), FastaId()) - ) align_results_map: dict[AccessionVersion, dict[SegmentName, list[str]]] = defaultdict(dict) input_unaligned_sequences: dict[ AccessionVersion, dict[SegmentName, NucleotideSequence | None] @@ -298,29 +316,17 @@ def assign_segment_with_nextclade_align( all_dfs = [] with TemporaryDirectory(delete=not config.keep_tmp_dir) as result_dir: input_file = result_dir + "/input.fasta" - os.makedirs(os.path.dirname(input_file), exist_ok=True) - for entry in unprocessed: - accession_version = entry.accessionVersion - input_unaligned_sequences[accession_version] = entry.data.unalignedNucleotideSequences - with open(input_file, "a", encoding="utf-8") as f: - for fastaId, seq in input_unaligned_sequences[accession_version].items(): - id = f"{accession_version}__{fastaId}" - id_map[id] = (accession_version, fastaId) - f.write(f">{id}\n") - f.write(f"{seq}\n") + input_unaligned_sequences, id_map = write_nextclade_input_fasta(unprocessed, input_file) for sequence_and_dataset in config.nucleotideSequences: segment = sequence_and_dataset.name - dataset_dir_seg = ( - dataset_dir if not config.multi_segment else dataset_dir + "/" + segment - ) - result_file_seg = result_dir + "/sort_output_" + segment + ".tsv" + result_file_seg = f"{result_dir}/sort_output_{segment}.tsv" command = [ "nextclade3", "run", f"--output-tsv={result_file_seg}", - f"--input-dataset={dataset_dir_seg}", + f"--input-dataset={dataset_dir}/{segment}", "--jobs=1", "--", input_file, @@ -336,62 +342,46 @@ def assign_segment_with_nextclade_align( all_dfs.append(df) df_combined = pd.concat(all_dfs, ignore_index=True) - no_hits = df_combined[df_combined["alignmentScore"].isna()] hits = ( df_combined.dropna(subset=["alignmentScore"]) .sort_values(by=["seqName", "alignmentScore"], ascending=[True, False]) .drop_duplicates(subset="seqName", keep="first") ) - for seq_name in no_hits["seqName"].unique(): - if seq_name not in hits["seqName"].unique(): - (accession_version, fastaId) = id_map[seq_name] - msg = ( - f"Sequence with fasta header {fastaId} does not align to any segment for" - f" organism: {config.organism} per `nextclade align`. " - f"Double check you are submitting to the correct organism." - ) - has_missing_segments[accession_version] = True - if config.alignment_requirement == AlignmentRequirement.ALL: - alerts.errors.setdefault(accession_version, []).append( - ProcessingAnnotation.from_single( - ProcessingAnnotationAlignment, - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - message=msg, - ) - ) - else: - alerts.warnings.setdefault(accession_version, []).append( - ProcessingAnnotation.from_single( - ProcessingAnnotationAlignment, - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - message=msg, - ) - ) + + seq_names = set(df_combined["seqName"].tolist()) + seq_names_with_hits = set(hits["seqName"].tolist()) + for seq_name in seq_names - seq_names_with_hits: + (accession_version, fasta_id) = id_map[seq_name] + has_missing_segments[accession_version] = True + annotation = sequence_annotation( + f"Sequence with fasta header {fasta_id} does not align to any segment for" + f" organism: {config.organism} per `nextclade align`. " + f"Double check you are submitting to the correct organism." + ) + if config.alignment_requirement == AlignmentRequirement.ALL: + alerts.errors[accession_version].append(annotation) + else: + alerts.warnings[accession_version].append(annotation) best_hits = hits.groupby("seqName", as_index=False).first() logger.debug(f"Found hits: {best_hits['seqName'].tolist()}") for _, row in best_hits.iterrows(): - (accession_version, fastaId) = id_map[row["seqName"]] + (accession_version, fasta_id) = id_map[row["seqName"]] for seg in config.nucleotideSequences: if row["segment"] == seg.name: - align_results_map[accession_version].setdefault(seg.name, []).append(fastaId) + align_results_map[accession_version].setdefault(seg.name, []).append(fasta_id) break continue for accession_version, segment_map in align_results_map.items(): for segment_name, headers in segment_map.items(): if len(headers) > 1: - msg = ( - f"Multiple sequences (with fasta headers: {', '.join(headers)}) align to " - f"{segment_name} - only one entry is allowed." - ) has_duplicate_segments[accession_version] = True - alerts.errors.setdefault(accession_version, []).append( - ProcessingAnnotation.from_single( - ProcessingAnnotationAlignment, - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - message=msg, + alerts.errors[accession_version].append( + sequence_annotation( + f"Multiple sequences (with fasta headers: {', '.join(headers)}) align to " + f"{segment_name} - only one entry is allowed." ) ) continue @@ -410,11 +400,9 @@ def assign_segment_with_nextclade_align( or config.alignment_requirement == AlignmentRequirement.ANY ) ): - alerts.errors.setdefault(accession_version, []).append( - ProcessingAnnotation.from_single( - ProcessingAnnotationAlignment, - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - message="No sequence data could be classified - " + alerts.errors[accession_version].append( + sequence_annotation( + "No sequence data could be classified - " "check you are submitting to the correct organism.", ) ) @@ -442,115 +430,69 @@ def assign_segment_with_nextclade_sort( has_missing_segments: dict[AccessionVersion, bool] = defaultdict(bool) has_duplicate_segments: dict[AccessionVersion, bool] = defaultdict(bool) - - id_map: defaultdict[str, tuple[AccessionVersion, FastaId]] = defaultdict( - lambda: (AccessionVersion(), FastaId()) - ) sort_results_map: dict[AccessionVersion, dict[SegmentName, list[str]]] = defaultdict(dict) - input_unaligned_sequences: dict[ - AccessionVersion, dict[SegmentName, NucleotideSequence | None] - ] = defaultdict(dict) + with TemporaryDirectory(delete=not config.keep_tmp_dir) as result_dir: input_file = result_dir + "/input.fasta" - os.makedirs(os.path.dirname(input_file), exist_ok=True) - for entry in unprocessed: - accession_version = entry.accessionVersion - input_unaligned_sequences[accession_version] = entry.data.unalignedNucleotideSequences - with open(input_file, "a", encoding="utf-8") as f: - for fastaId, seq in input_unaligned_sequences[accession_version].items(): - id = f"{accession_version}__{fastaId}" - id_map[id] = (accession_version, fastaId) - f.write(f">{id}\n") - f.write(f"{seq}\n") - - result_file = result_dir + "/sort_output.tsv" + input_unaligned_sequences, id_map = write_nextclade_input_fasta(unprocessed, input_file) + df = run_sort( - result_file, - input_file, - config, - config.nextclade_dataset_server, - dataset_dir, + result_file=result_dir + "/sort_output.tsv", + input_file=input_file, + dataset_dir=dataset_dir, ) - no_hits = df[df["score"].isna()] hits = df.dropna(subset=["score"]).sort_values("score", ascending=False) - for seq_name in no_hits["seqName"].unique(): - if seq_name not in hits["seqName"].unique(): - (accession_version, fastaId) = id_map[seq_name] - msg = ( - f"Sequence with fasta header {fastaId} does not appear to match any reference for" - f" organism: {config.organism} per `nextclade sort`. " - f"Double check you are submitting to the correct organism." - ) - has_missing_segments[accession_version] = True - if config.alignment_requirement == AlignmentRequirement.ALL: - alerts.errors.setdefault(accession_version, []).append( - ProcessingAnnotation.from_single( - ProcessingAnnotationAlignment, - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - message=msg, - ) - ) - else: - alerts.warnings.setdefault(accession_version, []).append( - ProcessingAnnotation.from_single( - ProcessingAnnotationAlignment, - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - message=msg, - ) - ) + + seq_names = set(df["seqName"].tolist()) + seq_names_with_hits = set(df.dropna(subset=["score"])["seqName"].tolist()) + for seq_name in seq_names - seq_names_with_hits: + (accession_version, fasta_id) = id_map[seq_name] + has_missing_segments[accession_version] = True + annotation = sequence_annotation( + f"Sequence with fasta header {fasta_id} does not appear to match any reference for" + f" organism: {config.organism} per `nextclade sort`. " + f"Double check you are submitting to the correct organism." + ) + if config.alignment_requirement == AlignmentRequirement.ALL: + alerts.errors[accession_version].append(annotation) + else: + alerts.warnings[accession_version].append(annotation) best_hits = hits.groupby("seqName", as_index=False).first() logger.debug(f"Found hits: {best_hits['seqName'].tolist()}") for _, row in best_hits.iterrows(): - (accession_version, fastaId) = id_map[row["seqName"]] + (accession_version, fasta_id) = id_map[row["seqName"]] not_found = True for segment in config.nucleotideSequences: # TODO: need to check that accepted_sort_matches does not overlap across segments - if row["dataset"] in segment.accepted_sort_matches: + if row["dataset"] in accepted_sort_matches_or_default(segment): not_found = False - sort_results_map[accession_version].setdefault(segment.name, []).append(fastaId) + sort_results_map[accession_version].setdefault(segment.name, []).append(fasta_id) break if not not_found: continue has_missing_segments[accession_version] = True - msg = ( - f"Sequence {fastaId} best matches {row['dataset']}, " + annotation = sequence_annotation( + f"Sequence {fasta_id} best matches {row['dataset']}, " "which is currently not an accepted option for organism: " f"{config.organism}. It is therefore not possible to release. " "Contact the administrator if you think this message is an error." ) if config.alignment_requirement == AlignmentRequirement.ALL: - alerts.errors[accession_version].append( - ProcessingAnnotation.from_single( - ProcessingAnnotationAlignment, - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - message=msg, - ) - ) + alerts.errors[accession_version].append(annotation) else: - alerts.warnings[accession_version].append( - ProcessingAnnotation.from_single( - ProcessingAnnotationAlignment, - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - message=msg, - ) - ) + alerts.warnings[accession_version].append(annotation) for accession_version, segment_map in sort_results_map.items(): for segment_name, headers in segment_map.items(): if len(headers) > 1: - msg = ( - f"Multiple sequences (with fasta headers: {', '.join(headers)}) align to " - f"{segment_name} - only one entry is allowed." - ) has_duplicate_segments[accession_version] = True alerts.errors.setdefault(accession_version, []).append( - ProcessingAnnotation.from_single( - ProcessingAnnotationAlignment, - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - message=msg, + sequence_annotation( + f"Multiple sequences (with fasta headers: {', '.join(headers)}) align " + f"to {segment_name} - only one entry is allowed." ) ) continue @@ -570,10 +512,8 @@ def assign_segment_with_nextclade_sort( ) ): alerts.errors.setdefault(accession_version, []).append( - ProcessingAnnotation.from_single( - ProcessingAnnotationAlignment, - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - message="No sequence data could be classified - " + sequence_annotation( + "No sequence data could be classified - " "check you are submitting to the correct organism.", ) ) @@ -595,21 +535,17 @@ def assign_single_segment( sequenceNameToFastaId: dict[SegmentName, str] = {} if len(input_unaligned_sequences) > 1: errors.append( - ProcessingAnnotation.from_single( - ProcessingAnnotationAlignment, - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - message=( - f"Multiple sequences: {list(input_unaligned_sequences.keys())} found in the" - f" input data, but organism: {config.organism} is single-segmented. " - "Please check that your metadata and sequences are annotated correctly." - "Each metadata entry should have a single corresponding fasta sequence " - "entry with the same submissionId." - ), + sequence_annotation( + f"Multiple sequences: {list(input_unaligned_sequences.keys())} found in the" + f" input data, but organism: {config.organism} is single-segmented. " + "Please check that your metadata and sequences are annotated correctly." + "Each metadata entry should have a single corresponding fasta sequence " + "entry with the same submissionId." ) ) else: - fastaHeader, value = next(iter(input_unaligned_sequences.items())) - sequenceNameToFastaId["main"] = fastaHeader + fasta_id, value = next(iter(input_unaligned_sequences.items())) + sequenceNameToFastaId["main"] = fasta_id unaligned_nucleotide_sequences["main"] = value return SegmentAssignment( unalignedNucleotideSequences=unaligned_nucleotide_sequences, @@ -677,14 +613,10 @@ def assign_segment_using_header( if len(unaligned_segment) > 1: duplicate_segments.update(unaligned_segment) errors.append( - ProcessingAnnotation.from_single( - ProcessingAnnotationAlignment, - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - message=( - f"Found multiple sequences with the same segment name: {segment}. " - "Each metadata entry can have multiple corresponding fasta sequence " - "entries with format _." - ), + sequence_annotation( + f"Found multiple sequences with the same segment name: {segment}. " + "Each metadata entry can have multiple corresponding fasta sequence " + "entries with format _." ) ) elif len(unaligned_segment) == 1: @@ -699,24 +631,18 @@ def assign_segment_using_header( ) if len(remaining_segments) > 0: errors.append( - ProcessingAnnotation.from_single( - ProcessingAnnotationAlignment, - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - message=( - f"Found sequences in the input data with segments that are not in the config: " - f"{', '.join(remaining_segments)}. " - "Each metadata entry can have multiple corresponding fasta sequence " - "entries with format _ valid segments are: " - f"{', '.join([seq.name for seq in config.nucleotideSequences])}." - ), + sequence_annotation( + f"Found sequences in the input data with segments that are not in the config: " + f"{', '.join(remaining_segments)}. " + "Each metadata entry can have multiple corresponding fasta sequence " + "entries with format _ valid segments are: " + f"{', '.join([seq.name for seq in config.nucleotideSequences])}." ) ) if len(unaligned_nucleotide_sequences) == 0 and not duplicate_segments: errors.append( - ProcessingAnnotation.from_single( - ProcessingAnnotationAlignment, - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - message="No sequence data found - ", + sequence_annotation( + "No sequence data found - ", ) ) return SegmentAssignment( @@ -841,10 +767,7 @@ def enrich_with_nextclade( # noqa: PLR0914 with TemporaryDirectory(delete=not config.keep_tmp_dir) as result_dir: for sequence_and_dataset in config.nucleotideSequences: segment = sequence_and_dataset.name - result_dir_seg = result_dir if not config.multi_segment else result_dir + "/" + segment - dataset_dir_seg = ( - dataset_dir if not config.multi_segment else dataset_dir + "/" + segment - ) + result_dir_seg = result_dir + "/" + segment input_file = result_dir_seg + "/input.fasta" os.makedirs(os.path.dirname(input_file), exist_ok=True) is_empty: bool = True @@ -859,19 +782,19 @@ def enrich_with_nextclade( # noqa: PLR0914 if config.require_nextclade_sort_match: alerts = check_nextclade_sort_matches( - result_dir_seg, - input_file, - alerts, - config, - sequence_and_dataset, - dataset_dir, + result_file_dir=result_dir_seg, + input_file=input_file, + alerts=alerts, + organism=config.organism, + accepted_sort_matches=accepted_sort_matches_or_default(sequence_and_dataset), + dataset_dir=dataset_dir, ) command = [ "nextclade3", "run", f"--output-all={result_dir_seg}", - f"--input-dataset={dataset_dir_seg}", + f"--input-dataset={dataset_dir}/{segment}", f"--output-translations={result_dir_seg}/nextclade.cds_translation.{{cds}}.fasta", "--jobs=1", "--", @@ -925,25 +848,21 @@ def enrich_with_nextclade( # noqa: PLR0914 def download_nextclade_dataset(dataset_dir: str, config: Config) -> None: for sequence_and_dataset in config.nucleotideSequences: - name = sequence_and_dataset.name - nextclade_dataset_name = sequence_and_dataset.nextclade_dataset_name - nextclade_dataset_server = ( - sequence_and_dataset.nextclade_dataset_server or config.nextclade_dataset_server - ) - - dataset_dir_seg = dataset_dir if not config.multi_segment else dataset_dir + "/" + name dataset_download_command = [ "nextclade3", "dataset", "get", - f"--name={nextclade_dataset_name}", - f"--server={nextclade_dataset_server}", - f"--output-dir={dataset_dir_seg}", + f"--name={sequence_and_dataset.nextclade_dataset_name}", + f"--server={ + sequence_and_dataset.nextclade_dataset_server or config.nextclade_dataset_server + }", + f"--output-dir={dataset_dir}/{sequence_and_dataset.name}", + *( + f"--tag={sequence_and_dataset.nextclade_dataset_tag}" + if sequence_and_dataset.nextclade_dataset_tag + else [] + ), ] - - if sequence_and_dataset.nextclade_dataset_tag is not None: - dataset_download_command.append(f"--tag={sequence_and_dataset.nextclade_dataset_tag}") - logger.info("Downloading Nextclade dataset: %s", dataset_download_command) if subprocess.run(dataset_download_command, check=False).returncode != 0: # noqa: S603 msg = "Dataset download failed" diff --git a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py index d418d76ee7..113f947692 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py @@ -35,6 +35,7 @@ ProcessingAnnotationAlignment, ProcessingResult, ProcessingSpec, + SegmentClassificationMethod, SegmentName, SubmissionData, UnprocessedAfterNextclade, @@ -665,8 +666,12 @@ def run(config: Config) -> None: with TemporaryDirectory(delete=not config.keep_tmp_dir) as dataset_dir: if config.alignment_requirement != AlignmentRequirement.NONE: download_nextclade_dataset(dataset_dir, config) - if config.minimizer_index: - download_minimizer(config.minimizer_index, dataset_dir + "/minimizer/minimizer.json") + if ( + config.minimizer_index + or config.segment_classification_method == SegmentClassificationMethod.MINIMIZER + or config.require_nextclade_sort_match + ): + download_minimizer(config, dataset_dir + "/minimizer/minimizer.json") total_processed = 0 etag = None last_force_refresh = time.time() diff --git a/preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/.gitignore b/preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/.gitignore new file mode 100644 index 0000000000..b030b02f54 --- /dev/null +++ b/preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/.gitignore @@ -0,0 +1 @@ +main/ \ No newline at end of file diff --git a/preprocessing/nextclade/tests/factory_methods.py b/preprocessing/nextclade/tests/factory_methods.py index 4d64b334b7..514b455d95 100644 --- a/preprocessing/nextclade/tests/factory_methods.py +++ b/preprocessing/nextclade/tests/factory_methods.py @@ -14,6 +14,7 @@ ProcessedEntry, ProcessedMetadataValue, ProcessingAnnotation, + ProcessingAnnotationAlignment, SegmentName, UnprocessedData, UnprocessedEntry, @@ -42,6 +43,15 @@ class ProcessingAnnotationHelper: message: str type: AnnotationSourceType = AnnotationSourceType.METADATA + @classmethod + def sequence_annotation_helper(cls, message: str) -> "ProcessingAnnotationHelper": + return cls( + unprocessed_field_names=[ProcessingAnnotationAlignment], + processed_field_names=[ProcessingAnnotationAlignment], + message=message, + type=AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + ) + @dataclass class ProcessedAlignment: diff --git a/preprocessing/nextclade/tests/multi_segment_config.yaml b/preprocessing/nextclade/tests/multi_segment_config.yaml index b888b0e483..a609b357f4 100644 --- a/preprocessing/nextclade/tests/multi_segment_config.yaml +++ b/preprocessing/nextclade/tests/multi_segment_config.yaml @@ -8,7 +8,6 @@ genes: log_level: DEBUG minimizer_index: TEST segment_classification_method: "minimizer" -nextclade_dataset_server: TEST nucleotideSequences: - name: ebola-sudan nextclade_dataset_name: ebola-dataset/ebola-sudan diff --git a/preprocessing/nextclade/tests/test_nextclade_preprocessing.py b/preprocessing/nextclade/tests/test_nextclade_preprocessing.py index ab916112c4..6f31580cde 100644 --- a/preprocessing/nextclade/tests/test_nextclade_preprocessing.py +++ b/preprocessing/nextclade/tests/test_nextclade_preprocessing.py @@ -1,6 +1,8 @@ # ruff: noqa: S101 +import os +import shutil from pathlib import Path from typing import Literal @@ -23,7 +25,6 @@ AnnotationSource, AnnotationSourceType, ProcessingAnnotation, - ProcessingAnnotationAlignment, SegmentClassificationMethod, SubmissionData, UnprocessedData, @@ -439,13 +440,10 @@ def invalid_sequence() -> str: }, expected_errors=build_processing_annotations( [ - ProcessingAnnotationHelper( - ["alignment"], - ["alignment"], + ProcessingAnnotationHelper.sequence_annotation_helper( "Sequence with fasta header fastaHeader1 does not align to any segment for " "organism: multi-ebola-test per `nextclade align`. " - "Double check you are submitting to the correct organism.", - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + "Double check you are submitting to the correct organism." ) ] ), @@ -479,14 +477,11 @@ def invalid_sequence() -> str: }, expected_errors=build_processing_annotations( [ - ProcessingAnnotationHelper( - ["alignment"], - ["alignment"], + ProcessingAnnotationHelper.sequence_annotation_helper( "Sequence with fasta header fastaHeader1 does not align to any segment for " "organism: multi-ebola-test per `nextclade align`. " - "Double check you are submitting to the correct organism.", - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - ), + "Double check you are submitting to the correct organism." + ) ] ), expected_warnings=[], @@ -526,13 +521,10 @@ def invalid_sequence() -> str: }, expected_errors=build_processing_annotations( [ - ProcessingAnnotationHelper( - ["alignment"], - ["alignment"], + ProcessingAnnotationHelper.sequence_annotation_helper( "Sequence with fasta header fastaHeader1 does not appear to match any reference" " for organism: multi-ebola-test per `nextclade sort`. " "Double check you are submitting to the correct organism.", - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, ) ] ), @@ -566,14 +558,11 @@ def invalid_sequence() -> str: }, expected_errors=build_processing_annotations( [ - ProcessingAnnotationHelper( - ["alignment"], - ["alignment"], - "Sequence with fasta header fastaHeader1 does not appear to match any reference" - " for organism: multi-ebola-test per `nextclade sort`. " - "Double check you are submitting to the correct organism.", - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - ), + ProcessingAnnotationHelper.sequence_annotation_helper( + "Sequence with fasta header fastaHeader1 does not appear to match any reference " + "for organism: multi-ebola-test per `nextclade sort`. " + "Double check you are submitting to the correct organism." + ) ] ), expected_warnings=[], @@ -613,24 +602,18 @@ def invalid_sequence() -> str: }, expected_errors=build_processing_annotations( [ - ProcessingAnnotationHelper( - [ProcessingAnnotationAlignment], - [ProcessingAnnotationAlignment], + ProcessingAnnotationHelper.sequence_annotation_helper( "No sequence data could be classified - " "check you are submitting to the correct organism.", - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, ) ] ), expected_warnings=build_processing_annotations( [ - ProcessingAnnotationHelper( - ["alignment"], - ["alignment"], + ProcessingAnnotationHelper.sequence_annotation_helper( "Sequence with fasta header fastaHeader1 does not appear to match any reference " "for organism: multi-ebola-test per `nextclade sort`. " "Double check you are submitting to the correct organism.", - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, ) ] ), @@ -664,13 +647,10 @@ def invalid_sequence() -> str: expected_errors=[], expected_warnings=build_processing_annotations( [ - ProcessingAnnotationHelper( - ["alignment"], - ["alignment"], + ProcessingAnnotationHelper.sequence_annotation_helper( "Sequence with fasta header fastaHeader1 does not appear to match any reference" " for organism: multi-ebola-test per `nextclade sort`. " "Double check you are submitting to the correct organism.", - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, ) ] ), @@ -710,24 +690,18 @@ def invalid_sequence() -> str: }, expected_errors=build_processing_annotations( [ - ProcessingAnnotationHelper( - [ProcessingAnnotationAlignment], - [ProcessingAnnotationAlignment], + ProcessingAnnotationHelper.sequence_annotation_helper( "No sequence data could be classified - " "check you are submitting to the correct organism.", - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, ) ] ), expected_warnings=build_processing_annotations( [ - ProcessingAnnotationHelper( - ["alignment"], - ["alignment"], + ProcessingAnnotationHelper.sequence_annotation_helper( "Sequence with fasta header fastaHeader1 does not align to any segment for " "organism: multi-ebola-test per `nextclade align`. " "Double check you are submitting to the correct organism.", - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, ) ] ), @@ -761,13 +735,10 @@ def invalid_sequence() -> str: expected_errors=[], expected_warnings=build_processing_annotations( [ - ProcessingAnnotationHelper( - ["alignment"], - ["alignment"], + ProcessingAnnotationHelper.sequence_annotation_helper( "Sequence with fasta header fastaHeader1 does not align to any segment for " "organism: multi-ebola-test per `nextclade align`. " "Double check you are submitting to the correct organism.", - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, ) ] ), @@ -802,15 +773,12 @@ def invalid_sequence() -> str: expected_metadata={"length": 0}, expected_errors=build_processing_annotations( [ - ProcessingAnnotationHelper( - [ProcessingAnnotationAlignment], - [ProcessingAnnotationAlignment], + ProcessingAnnotationHelper.sequence_annotation_helper( "Multiple sequences: ['fastaHeader1', 'fastaHeader2'] found in the" " input data, but organism: ebola-sudan-test is single-segmented. " "Please check that your metadata and sequences are annotated correctly." "Each metadata entry should have a single corresponding fasta sequence " "entry with the same submissionId.", - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, ), ] ), @@ -847,12 +815,9 @@ def invalid_sequence() -> str: }, expected_errors=build_processing_annotations( [ - ProcessingAnnotationHelper( - [ProcessingAnnotationAlignment], - [ProcessingAnnotationAlignment], + ProcessingAnnotationHelper.sequence_annotation_helper( "Multiple sequences (with fasta headers: duplicate_ebola-sudan, ebola-sudan) " "align to ebola-sudan - only one entry is allowed.", - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, ), ] ), @@ -911,13 +876,10 @@ def invalid_sequence() -> str: }, expected_errors=build_processing_annotations( [ - ProcessingAnnotationHelper( - [ProcessingAnnotationAlignment], - [ProcessingAnnotationAlignment], + ProcessingAnnotationHelper.sequence_annotation_helper( "Found multiple sequences with the same segment name: ebola-sudan. " "Each metadata entry can have multiple corresponding fasta sequence " "entries with format _.", - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, ) ] ), @@ -945,14 +907,11 @@ def invalid_sequence() -> str: }, expected_errors=build_processing_annotations( [ - ProcessingAnnotationHelper( - [ProcessingAnnotationAlignment], - [ProcessingAnnotationAlignment], + ProcessingAnnotationHelper.sequence_annotation_helper( "Found sequences in the input data with segments that are not in the config: " "unknown_segment. Each metadata entry can have multiple corresponding fasta " "sequence entries with format _ valid segments are: " "ebola-sudan, ebola-zaire.", - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, ) ] ), @@ -969,6 +928,19 @@ def invalid_sequence() -> str: ] +def copy_dataset(source_folder: str, destination_folder: str) -> None: + os.makedirs(destination_folder, exist_ok=True) + + for item in os.listdir(source_folder): + src_path = os.path.join(source_folder, item) + + if not os.path.isfile(src_path): + continue + + dst_path = os.path.join(destination_folder, item) + shutil.copy2(src_path, dst_path) + + def process_single_entry( test_case: ProcessingTestCase, config: Config, dataset_dir: str = "temp" ) -> SubmissionData: @@ -985,6 +957,8 @@ def test_preprocessing_single_segment(test_case_def: Case): config = get_config(SINGLE_SEGMENT_CONFIG, ignore_args=True) factory_custom = ProcessedEntryFactory(all_metadata_fields=list(config.processing_spec.keys())) test_case = test_case_def.create_test_case(factory_custom) + # process_single expects dataset to be in a subfolder with the segment name (in this case "main") + copy_dataset(EBOLA_SUDAN_DATASET, os.path.join(EBOLA_SUDAN_DATASET, "main")) processed_entry = process_single_entry(test_case, config, EBOLA_SUDAN_DATASET) verify_processed_entry( processed_entry.processed_entry, test_case.expected_output, test_case.name @@ -1078,13 +1052,6 @@ def test_preprocessing_multi_segment_none_requirement(test_case_def: Case): ) -def test_config_accepted_sort_matches() -> None: - config = get_config(MULTI_SEGMENT_CONFIG, ignore_args=True) - accepted_fields = config.nucleotideSequences[0].accepted_sort_matches - expected_fields = {"ebola-dataset/ebola-sudan", "ebola-sudan", "accepted-name"} - assert set(accepted_fields) == expected_fields - - def test_preprocessing_without_metadata() -> None: config = get_config(MULTI_SEGMENT_CONFIG, ignore_args=True) sequence_entry_data = UnprocessedEntry( From 6d14d8aa9f3a603797c74c8ebef2245b9ef32a66 Mon Sep 17 00:00:00 2001 From: Cornelius Roemer Date: Thu, 27 Nov 2025 16:28:31 +0100 Subject: [PATCH 25/44] Separate test datasets, don't copy in tests --- .../ebola-dataset/ebola-sudan/.gitignore | 1 - .../{ => main}/genome_annotation.gff3 | 0 .../ebola-sudan/{ => main}/pathogen.json | 0 .../ebola-sudan/{ => main}/reference.fasta | 0 .../{ => main}/genome_annotation.gff3 | 0 .../ebola-zaire/{ => main}/pathogen.json | 0 .../ebola-zaire/{ => main}/reference.fasta | 0 .../ebola-sudan/genome_annotation.gff3 | 19 +++++++++++++++ .../ebola-sudan/pathogen.json | 24 +++++++++++++++++++ .../ebola-sudan/reference.fasta | 2 ++ .../ebola-zaire/genome_annotation.gff3 | 19 +++++++++++++++ .../ebola-zaire/pathogen.json | 24 +++++++++++++++++++ .../ebola-zaire/reference.fasta | 2 ++ .../minimizer/minimizer.json | 0 .../tests/test_nextclade_preprocessing.py | 21 ++-------------- 15 files changed, 92 insertions(+), 20 deletions(-) delete mode 100644 preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/.gitignore rename preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/{ => main}/genome_annotation.gff3 (100%) rename preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/{ => main}/pathogen.json (100%) rename preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/{ => main}/reference.fasta (100%) rename preprocessing/nextclade/tests/ebola-dataset/ebola-zaire/{ => main}/genome_annotation.gff3 (100%) rename preprocessing/nextclade/tests/ebola-dataset/ebola-zaire/{ => main}/pathogen.json (100%) rename preprocessing/nextclade/tests/ebola-dataset/ebola-zaire/{ => main}/reference.fasta (100%) create mode 100644 preprocessing/nextclade/tests/ebola-multipath-dataset/ebola-sudan/genome_annotation.gff3 create mode 100644 preprocessing/nextclade/tests/ebola-multipath-dataset/ebola-sudan/pathogen.json create mode 100644 preprocessing/nextclade/tests/ebola-multipath-dataset/ebola-sudan/reference.fasta create mode 100644 preprocessing/nextclade/tests/ebola-multipath-dataset/ebola-zaire/genome_annotation.gff3 create mode 100644 preprocessing/nextclade/tests/ebola-multipath-dataset/ebola-zaire/pathogen.json create mode 100644 preprocessing/nextclade/tests/ebola-multipath-dataset/ebola-zaire/reference.fasta rename preprocessing/nextclade/tests/{ebola-dataset => ebola-multipath-dataset}/minimizer/minimizer.json (100%) diff --git a/preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/.gitignore b/preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/.gitignore deleted file mode 100644 index b030b02f54..0000000000 --- a/preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/.gitignore +++ /dev/null @@ -1 +0,0 @@ -main/ \ No newline at end of file diff --git a/preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/genome_annotation.gff3 b/preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/main/genome_annotation.gff3 similarity index 100% rename from preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/genome_annotation.gff3 rename to preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/main/genome_annotation.gff3 diff --git a/preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/pathogen.json b/preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/main/pathogen.json similarity index 100% rename from preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/pathogen.json rename to preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/main/pathogen.json diff --git a/preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/reference.fasta b/preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/main/reference.fasta similarity index 100% rename from preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/reference.fasta rename to preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/main/reference.fasta diff --git a/preprocessing/nextclade/tests/ebola-dataset/ebola-zaire/genome_annotation.gff3 b/preprocessing/nextclade/tests/ebola-dataset/ebola-zaire/main/genome_annotation.gff3 similarity index 100% rename from preprocessing/nextclade/tests/ebola-dataset/ebola-zaire/genome_annotation.gff3 rename to preprocessing/nextclade/tests/ebola-dataset/ebola-zaire/main/genome_annotation.gff3 diff --git a/preprocessing/nextclade/tests/ebola-dataset/ebola-zaire/pathogen.json b/preprocessing/nextclade/tests/ebola-dataset/ebola-zaire/main/pathogen.json similarity index 100% rename from preprocessing/nextclade/tests/ebola-dataset/ebola-zaire/pathogen.json rename to preprocessing/nextclade/tests/ebola-dataset/ebola-zaire/main/pathogen.json diff --git a/preprocessing/nextclade/tests/ebola-dataset/ebola-zaire/reference.fasta b/preprocessing/nextclade/tests/ebola-dataset/ebola-zaire/main/reference.fasta similarity index 100% rename from preprocessing/nextclade/tests/ebola-dataset/ebola-zaire/reference.fasta rename to preprocessing/nextclade/tests/ebola-dataset/ebola-zaire/main/reference.fasta diff --git a/preprocessing/nextclade/tests/ebola-multipath-dataset/ebola-sudan/genome_annotation.gff3 b/preprocessing/nextclade/tests/ebola-multipath-dataset/ebola-sudan/genome_annotation.gff3 new file mode 100644 index 0000000000..8bea211253 --- /dev/null +++ b/preprocessing/nextclade/tests/ebola-multipath-dataset/ebola-sudan/genome_annotation.gff3 @@ -0,0 +1,19 @@ +##gff-version 3 +#!gff-spec-version 1.21 +#!processor NCBI annotwriter +##sequence-region NC_006432.1 1 18875 +##species https://www.ncbi.nlm.nih.gov/Taxonomy/Browser/wwwtax.cgi?id=186540 +NC_006432.1 RefSeq region 1 18875 . + . ID=NC_006432.1:1..18875;Dbxref=taxon:186540;country=Uganda;gbkey=Src;isolate=Sudan virus/H.sapiens-tc/UGA/2000/Gulu-808892;isolation-source=human;mol_type=viral cRNA;note=isolated in 2000 +NC_006432.1 RefSeq five_prime_UTR 1 55 . + . ID=id-NC_006432.1:1..55;Note=leader region;function=regulation of initiation of RNA replication;gbkey=5'UTR +NC_006432.1 RefSeq gene 56 3007 . + . ID=gene-SEVgp1;Dbxref=GeneID:3160777;Name=NPEbolaSudan;gbkey=Gene;gene=NPEbolaSudan;gene_biotype=protein_coding;locus_tag=SEVgp1 +NC_006432.1 RefSeq mRNA 56 3007 . + . ID=rna-SEVgp1;Parent=gene-SEVgp1;Dbxref=GeneID:3160777;gbkey=mRNA;gene=NPEbolaSudan;locus_tag=SEVgp1;product=nucleoprotein +NC_006432.1 RefSeq exon 56 3007 . + . ID=exon-SEVgp1-1;Parent=rna-SEVgp1;Dbxref=GeneID:3160777;gbkey=mRNA;gene=NPEbolaSudan;locus_tag=SEVgp1;product=nucleoprotein +NC_006432.1 RefSeq CDS 458 2674 . + 0 ID=cds-YP_138520.1;Parent=rna-SEVgp1;Dbxref=GenBank:YP_138520.1,GeneID:3160777;Name=NPEbolaSudan;Note=predominant component of nucleocapsid;gbkey=CDS;gene=NPEbolaSudan;locus_tag=SEVgp1;product=nucleoprotein;protein_id=YP_138520.1 +NC_006432.1 RefSeq regulatory_region 56 67 . + . ID=id-SEVgp1;Parent=gene-SEVgp1;Dbxref=GeneID:3160777;Note=predicted transcription start site;gbkey=regulatory;gene=NPEbolaSudan;locus_tag=SEVgp1;regulatory_class=other +NC_006432.1 RefSeq polyA_signal_sequence 2496 3007 . + . ID=id-SEVgp1-2;Parent=gene-SEVgp1;Dbxref=GeneID:3160777;gbkey=regulatory;gene=NPEbolaSudan;locus_tag=SEVgp1;regulatory_class=polyA_signal_sequence +NC_006432.1 RefSeq gene 3013 4382 . + . ID=gene-SEVgp2;Dbxref=GeneID:3160776;Name=VP35EbolaSudan;gbkey=Gene;gene=VP35EbolaSudan;gene_biotype=protein_coding;locus_tag=SEVgp2 +NC_006432.1 RefSeq mRNA 3013 4382 . + . ID=rna-SEVgp2;Parent=gene-SEVgp2;Dbxref=GeneID:3160776;gbkey=mRNA;gene=VP35EbolaSudan;locus_tag=SEVgp2;product=VP35 +NC_006432.1 RefSeq exon 3013 4382 . + . ID=exon-SEVgp2-1;Parent=rna-SEVgp2;Dbxref=GeneID:3160776;gbkey=mRNA;gene=VP35EbolaSudan;locus_tag=SEVgp2;product=VP35 +NC_006432.1 RefSeq CDS 3138 4127 . + 0 ID=cds-YP_138521.1;Parent=rna-SEVgp2;Dbxref=GenBank:YP_138521.1,GeneID:3160776;Name=VP35EbolaSudan;Note=cofactor in polymerase complex%3B type I IFN antagonist;gbkey=CDS;gene=VP35EbolaSudan;locus_tag=SEVgp2;product=polymerase complex protein;protein_id=YP_138521.1 +NC_006432.1 RefSeq regulatory_region 3013 3024 . + . ID=id-SEVgp2;Parent=gene-SEVgp2;Dbxref=GeneID:3160776;Note=predicted transcription start site;gbkey=regulatory;gene=VP35EbolaSudan;locus_tag=SEVgp2;regulatory_class=other +NC_006432.1 RefSeq polyA_signal_sequence 4372 4382 . + . ID=id-SEVgp2-2;Parent=gene-SEVgp2;Dbxref=GeneID:3160776;gbkey=regulatory;gene=VP35EbolaSudan;locus_tag=SEVgp2;regulatory_class=polyA_signal_sequence diff --git a/preprocessing/nextclade/tests/ebola-multipath-dataset/ebola-sudan/pathogen.json b/preprocessing/nextclade/tests/ebola-multipath-dataset/ebola-sudan/pathogen.json new file mode 100644 index 0000000000..a1f443020d --- /dev/null +++ b/preprocessing/nextclade/tests/ebola-multipath-dataset/ebola-sudan/pathogen.json @@ -0,0 +1,24 @@ +{ + "attributes": { + "name": "Ebolavirus Sudan", + "reference accession": "NC_006432.1", + "reference name": "UGA/2000/Gulu-808892" + }, + "compatibility": { + "cli": "3.0.0-alpha.0", + "web": "3.0.0-alpha.0" + }, + "deprecated": false, + "enabled": true, + "experimental": true, + "files": { + "genomeAnnotation": "genome_annotation.gff3", + "pathogenJson": "pathogen.json", + "reference": "reference.fasta" + }, + "official": false, + "qc": {}, + "schemaVersion": "3.0.0", + "shortcuts": [ + ] +} diff --git a/preprocessing/nextclade/tests/ebola-multipath-dataset/ebola-sudan/reference.fasta b/preprocessing/nextclade/tests/ebola-multipath-dataset/ebola-sudan/reference.fasta new file mode 100644 index 0000000000..ab4a2784cc --- /dev/null +++ b/preprocessing/nextclade/tests/ebola-multipath-dataset/ebola-sudan/reference.fasta @@ -0,0 +1,2 @@ +>NC_006432.1 Sudan ebolavirus isolate Sudan virus/H.sapiens-tc/UGA/2000/Gulu-808892 +CGGACACACAAAAAGAAAGAAAAGTTTTTTATACTTTTTGTGTGCGAATAACTATGAGGAAGATTAATCATTTTCCTCAAACTCAAACTAATATTAACATTGAGATTGATCTCATCATTTACCAATTGGAGACAATTTAACTAGTCAATCCCCCATTTGGGGGCATTCCTAAAGTGTTGCAAAGGTATGTGGGTCGTATTGCTTTGCCTTTTCCTAACCTGGCTCCTCCTACAATTCTAACCTGCTTGATAAGTGTGATTACCTGAGTAATAGACTAATTTCGTCCTGGTAATTAGCATTTTCTAGTAAAACCAATACTATCTCAAGTCCTAAGAGAAGGTGAGAAGAGGGTCCCGAGGTATCCCTCCAGTCCACAAAATCTAGCTAATTTTAGCTGAGTGGACTGATTACTCTCATCACACGCTAACTACTAAGGGTTTACCTGAGAGCCTACAACATGGATAAACGGGTGAGAGGTTCATGGGCCCTGGGAGGACAATCTGAAGTTGATCTTGACTACCACAAAATATTAACAGCCGGGCTTTCGGTCCAACAAGGGATTGTGCGACAAAGAGTCATCCCGGTATATGTTGTGAGTGATCTTGAGGGTATTTGTCAACATATCATTCAGGCCTTTGAAGCAGGCGTAGATTTCCAAGATAATGCTGACAGCTTCCTTTTACTTTTATGTTTACATCATGCTTACCAAGGAGATCATAGGCTCTTCCTCAAAAGTGATGCAGTTCAATACTTAGAGGGCCATGGTTTCAGGTTTGAGGTCCGAGAAAAGGAGAATGTGCACCGTCTGGATGAATTGTTGCCCAATGTCACCGGTGGAAAAAATCTTAGGAGAACATTGGCTGCAATGCCTGAAGAGGAGACAACAGAAGCTAATGCTGGTCAGTTTTTATCCTTTGCCAGTTTGTTTCTACCCAAACTTGTCGTTGGGGAGAAAGCGTGTCTGGAAAAAGTACAAAGGCAGATTCAGGTCCATGCAGAACAAGGGCTCATTCAATATCCAACTTCCTGGCAATCAGTTGGACACATGATGGTGATCTTCCGTTTGATGAGAACAAACTTTTTAATCAAGTTCCTACTAATACATCAGGGGATGCACATGGTCGCAGGCCATGATGCGAATGACACAGTAATATCTAATTCTGTTGCCCAAGCAAGGTTCTCTGGTCTTCTGATTGTAAAGACTGTTCTGGACCACATCCTACAAAAAACAGATCTTGGAGTACGACTTCATCCACTGGCCAGGACAGCAAAAGTCAAGAATGAGGTCAGTTCATTCAAGGCAGCTCTTGGCTCACTTGCCAAGCATGGAGAATATGCTCCATTTGCACGTCTCCTCAATCTTTCTGGAGTCAACAACTTGGAACATGGGCTTTATCCACAACTCTCAGCCATTGCTTTGGGTGTTGCAACTGCCCACGGGAGCACGCTGGCTGGTGTTAATGTAGGGGAGCAATATCAGCAACTGCGTGAGGCTGCTACTGAAGCTGAAAAGCAACTCCAACAATATGCTGAAACACGTGAGTTGGACAACCTTGGGCTTGATGAACAGGAAAAGAAGATTCTCATGAGCTTCCACCAGAAGAAGAATGAGATCAGCTTCCAGCAAACTAACGCAATGGTAACCCTGAGGAAAGAGCGGCTGGCCAAACTGACCGAAGCCATCACGACTGCATCAAAGATCAAGGTTGGAGATCGTTATCCTGATGACAATGATATTCCATTTCCCGGGCCGATCTATGATGAAACCCACCCCAACCCTTCTGATGATAATCCTGATGATTCACGTGATACAACTATCCCAGGTGGTGTTGTTGACCCGTATGATGATGAGAGTAATAATTATCCTGACTACGAGGATTCGGCTGAAGGCACCACAGGAGATCTTGATCTCTTCAATTTGGACGACGACGATGACGACAGCCAACCAGGACCACCAGACAGGGGGCAGAGCAAGGAAAGAGCGGCTCGGACACATGGCCTCCAAGATCCGACCTTGGACGGAGCGAAAAAGGTGCCGGAGTTGACCCCAGGTTCCCACCAACCAGGCAACCTCCACATCACCAAGCCGGGTTCAAACACCAACCAACCACAAGGCAATATGTCATCTACTCTCCAGAGTATGACCCCTATACAGGAAGAATCAGAGCCCGATGATCAGAAAGATGATGATGACGAGAGTCTCACATCCCTTGACTCTGAAGGTGACGAAGATGTTGAGAGCGTATCAGGGGAGAACAACCCAACTGTAGCTCCACCAGCACCAGTCTACAAAGATACTGGAGTAGACACTAATCAGCAAAATGGACCAAGCAATGCTGTAGATGGTCAAGGTTCTGAAAGTGAAGCTCTCCCAATCAACCCCGAAAAGGGATCTGCACTGGAAGAAACATATTATCATCTCCTAAAAACACAGGGTCCATTTGAGGCAATCAATTATTATCACCTAATGAGTGATGAGCCCATTGCTTTTAGCACTGAAAGTGGCAAGGAATATATCTTCCCAGATTCTCTTGAAGAAGCCTACCCGCCTTGGTTGAGTGAGAAGGAGGCCTTAGAGAAAGAAAATCGTTATCTGGTCATTGATGGCCAGCAATTCCTCTGGCCAGTAATGAGCCTACAGGACAAGTTCCTTGCTGTTCTTCAACATGACTGAGGACCCATGATTAGTAGATTTTGTTTATTCTGAGCTTGATTATAATTGTTTTGATAATTCAAGTATGAGCAACCAACCCGAAATATAAACCCTATTTTAGTTATGAGGAAATTAAATAAATAATCTGTAAGTTGTAGGACTATGAAGAGCTGCTTGTGTCAATTTATCACGGGCTAATACCCATACCGCAAGAATAATTATTTAGTAATTTTGATCAGCTTATGATATGTACCAATAGGAAAACATTATAGCATTAAAACATAAAGTATCCTTCGATGAGCTTAGGAGGATAATATCCTGATGAATTCTATAGAACTTAGGATTAAGAAAAAATTCATGATGAAGATTAAAACCTTCATCATCCTTTAAAAAGAGAGCTATTCTTTATCTGAATGTCCTTATTAATGTCTAAGAGCTATTATTTTGTACCCTCTTAGCCTAGACACTGCCCAGCATATAAGCCATGCAGCAGGATAGGACTTATAGACATCATGGACCCGAAGTGTCTGGCTGGTTTTCTGAGCAATTAATGACCGGCAAAATACCGCTAACAGAGGTGTTTGTTGATGTTGAAAACAAACCAAGTCCTGCCCCGATAACCATTATTAGTAAGAATCCCAAGACAACACGTAAAAGTGATAAGCAAGTCCAAACAGATGATGCCAGTAGCTTATTGACAGAAGAAGTCAAGGCTGCCATAAATTCGGTGATATCAGCTGTGCGTCGGCAAACCAATGCTATTGAATCACTAGAAGGTCGAGTAACAACTCTTGAGGCCAGCTTAAAACCCGTTCAAGACATGGCAAAGACCATATCATCCCTGAATCGCAGCTGTGCCGAAATGGTTGCAAAATACGACCTACTGGTGATGACCACTGGGCGAGCAACTGCCACTGCTGCAGCAACAGAAGCATATTGGAATGAACATGGACAAGCACCTCCAGGCCCATCATTGTACGAGGATGATGCTATTAAGGCTAAATTGAAAGATCCGAACGGGAAGGTTCCAGAAAGTGTCAAACAGGCCTACATAAATCTAGATAGCACAAGTGCCCTCAATGAGGAAAATTTCGGGCGACCTTACATTTCAGCAAAAGATCTCAAGGAAATCATCTATGACCATCTCCCAGGATTTGGGACAGCTTTTCATCAGTTGGTGCAGGTTATCTGCAAAATTGGTAAGGATAATAATATCCTAGACATAATTCATGCAGAATTCCAAGCAAGCTTGGCTGAGGGAGACTCCCCCCAGTGTGCATTAATCCAGATAACAAAACGGATCCCTGCTTTCCAAGATGCCTCTCCTCCAATTGTGCATATCAAGTCTCGAGGAGATATACCCAAAGCCTGTCAGAAAAGCCTCCGGCCGGTCCCACCGTCACCAAAGATCGATAGAGGTTGGGTCTGTATTTTTCAATTCCAAGACGGGAAGGCCCTTGGGCTAAAAATATGATACAGAAGCAAGGTAAGCTCATTTTGCGATGGCCAAATGATACTTATGACTGTTTAAAATCAAGTTAGACTAATAGTCTATTGTGTCATAAGCTTATAAGTCAGTTTTAAATTTCCCCTCTATCCTAATCAATTGATAATGCTGTCAATAGGGAAATTCCCCTGTATTGTAATAAGACCTCATTAACATATTTCCCCTGCTTAGTACTATGCAGAAACCCCCGAGCAAATTAAAATTGATGAAGATTAAGAAAAAGAGGGATTTTCTCAGGAAAAATCTTTTTTCTTACCTTCATCTCATTTAAACAAATTTAGGACTCAGGAAAAATGAGAAGGGTCACTGTGCCGACTGCACCACCTGCCTATGCTGACATTGGCTATCCTATGAGCATGCTTCCCATCAAGTCAAGCAGGGCTGTGAGTGGAATTCAACAGAAACAAGAGGTCCTTCCTGGAATGGATACACCATCAAATTCTATGAGACCTGTTGCTGATGATAACATTGATCATACAAGTCATACCCCGAACGGAGTGGCCTCAGCATTCATCTTGGAGGCAACTGTCAATGTGATCTCGGGGCCCAAAGTCCTCATGAAACAAATCCCTATTTGGTTGCCACTCGGAATTGCTGACCAAAAAACGTACAGTTTTGACTCAACAACAGCAGCAATTATGCTCGCATCTTATACGATCACCCATTTTGGAAAGGCCAACAACCCCCTCGTTAGAGTGAATCGACTTGGTCAGGGAATACCGGATCACCCACTCAGATTGCTCAGGATGGGGAACCAGGCTTTCCTTCAAGAGTTTGTGCTACCACCAGTTCAACTGCCGCAATATTTCACTTTTGATCTGACTGCACTCAAACTAGTGACACAGCCTCTCCCTGCTGCAACATGGACAGATGAGACTCCGAGCAACCTTTCAGGAGCCCTTCGTCCCGGGCTTTCATTTCACCCAAAGCTGAGACCCGTTCTACTTCCAGGCAAGACGGGAAAGAAAGGGCATGTTTCTGATCTGACTGCCCCAGACAAAATTCAGACAATTGTGAACCTGATGCAAGATTTCAAAATCGTGCCAATTGATCCAGCTAAGAGTATCATTGGGATCGAGGTTCCAGAATTGCTGGTCCACAAGCTCACTGGGAAGAAAATGAGTCAGAAGAATGGACAGCCTATAATTCCTGTCTTACTCCCAAAATACATTGGGCTAGATCCAATCTCACCTGGAGACCTGACTATGGTCATAACACCAGATTATGATGATTGTCATTCACCTGCCAGTTGCTCTTATCTCAGTGAAAAGTGATTCTCACAAAGTGAGAGAAACACCTCCAGTAAAGAAATCAAATCTTATCTATAGCAACTCAATCGACTTTTAGGAAGCTAGCAGTCCATATACTATGGGACAACTCAACCCTCTTGTTAAAATGTACTAATCGGGTCAAGGAACTCTCACTGATCAAGCCTGAATCCAAGATAGAACCAGCCCAAAGGGCCTCCCCAGAGTCTCTTACAAGCTTAGCCAATCAATTAACATGCATAAGCGATCCATACTTCGCCCAATCAGTGTCCGATGTTCACCCCTTCAAGCCTCCTTCCTAGCAAATTGACCTAGCTGTACCAAGAGATTCCCTCAGCCTCCTTCTCAAATAACCTGATCCTCGAGGGTTACACCTTCACCACTCTATGCTCATTTCACCCAAACATAAAATGAAATGTCTTAACATGATTGCACCATTAAGAAAAACAAATCTGATGAAGATTAAGCCTGATGAAGGCCCAACCTTCATCTTTTTACCATAATCTTGTTCTCAGTACCATTTGATAAGGGTACACTTGCCAATACGCCCCCATCCTAAGGGTCTCGCAATGGGGGGTCTTAGCCTACTCCAATTGCCCAGGGACAAATTTCGGAAAAGCTCTTTCTTTGTTTGGGTCATCATCTTATTCCAAAAGGCCTTTTCCATGCCTTTGGGTGTTGTGACTAACAGCACTTTAGAAGTAACAGAGATTGACCAGCTAGTCTGCAAGGATCATCTTGCATCTACTGACCAGCTGAAATCAGTTGGTCTCAACCTCGAGGGGAGCGGAGTATCTACTGATATCCCATCTGCAACAAAGCGTTGGGGCTTCAGATCTGGTGTTCCTCCCAAGGTGGTCAGCTATGAAGCGGGAGAATGGGCTGAAAATTGCTACAATCTTGAAATAAAGAAGCCGGACGGGAGCGAATGCTTACCCCCACCGCCAGATGGTGTCAGAGGCTTTCCAAGGTGCCGCTATGTTCACAAAGCCCAAGGAACCGGGCCCTGCCCAGGTGACTACGCCTTTCACAAGGATGGAGCTTTCTTCCTCTATGACAGGCTGGCTTCAACTGTAATTTACAGAGGAGTCAATTTTGCTGAGGGGGTAATTGCATTCTTGATATTGGCTAAACCAAAAGAAACGTTCCTTCAGTCACCCCCCATTCGAGAGGCAGTAAACTACACTGAAAATACATCAAGTTATTATGCCACATCCTACTTGGAGTATGAAATCGAAAATTTTGGTGCTCAACACTCCACGACCCTTTTCAAAATTGACAATAATACTTTTGTTCGTCTGGACAGGCCCCACACGCCTCAGTTCCTTTTCCAGCTGAATGATACCATTCACCTTCACCAACAGTTGAGTAATACAACTGGGAGACTAATTTGGACACTAGATGCTAATATCAATGCTGATATTGGTGAATGGGCTTTTTGGGAAAATAAAAAAATCTCTCCGAACAACTACGTGGAGAAGAGCTGTCTTTCGAAGCTTTATCGCTCAACGAGACAGAAGACGATGATGCGGCATCGTCGAGAATTACAAAGGGAAGAATCTCCGACCGGGCCACCAGGAAGTATTCGGACCTGGTTCCAAAGAATTCCCCTGGGATGGTTCCATTGCACATACCAGAAGGGGAAACAACATTGCCGTCTCAGAATTCGACAGAAGGTCGAAGAGTAGGTGTGAACACTCAGGAGACCATTACAGAGACAGCTGCAACAATTATAGGCACTAACGGCAACCATATGCAGATCTCCACCATCGGGATAAGACCGAGCTCCAGCCAAATCCCGAGTTCCTCACCGACCACGGCACCAAGCCCTGAGGCTCAGACCCCCACAACCCACACATCAGGTCCATCAGTGATGGCCACCGAGGAACCAACAACACCACCGGGAAGCTCCCCCGGCCCAACAACAGAAGCACCCACTCTCACCACCCCAGAAAATATAACAACAGCGGTTAAAACTGTCCTGCCACAGGAGTCCACAAGCAACGGTCTAATAACTTCAACAGTAACAGGGATTCTTGGGAGTCTTGGGCTTCGAAAACGCAGCAGAAGACAAACTAACACCAAAGCCACGGGTAAGTGCAATCCCAACTTACACTACTGGACTGCACAAGAACAACATAATGCTGCTGGGATTGCCTGGATCCCGTACTTTGGACCGGGTGCGGAAGGCATATACACTGAAGGCCTGATGCATAACCAAAATGCCTTAGTCTGTGGACTTAGGCAACTTGCAAATGAAACAACTCAAGCTCTGCAGCTTTTCTTAAGAGCCACAACGGAGCTGCGGACATATACCATACTCAATAGGAAGGCCATAGATTTCCTTCTGCGACGATGGGGCGGGACATGCAGGATCCTGGGACCAGATTGTTGCATTGAGCCACATGATTGGACAAAAAACATCACTGATAAAATCAACCAAATCATCCATGATTTCATCGACAACCCCTTACCTAATCAGGATAATGATGATAATTGGTGGACGGGCTGGAGACAGTGGATCCCTGCAGGAATAGGCATTACTGGAATTATTATTGCAATTATTGCTCTTCTTTGCGTTTGCAAGCTGCTTTGCTGAATATCAATTTGAATCATCAATTTAAGCTTGATACATTTCTAGCATTTTAAATTATAAACCGATACTGATACTTGAAAATCAGGCTAATGCCAAGTTCTGTGCAAAACTTGAAAGTAGGTTTACAAAAATCCTTTGGACTGGAATGCTTTGATACTCTTTCTCAATACTATATAAGTTCCTTCCCAAGAATAATATTGATGAAGATTAAGAAAAAGTGACATTGTGCCCACTTTTGTAATCTTCATCCACCTACACATTCATATTCAGGAATCTTTGAATTAACCCTCACACTTGCTTAGGAAAGAGCCTATCCTCTACAAGAATCCCGAGGCGGCAATTCAGTTAATTTCATATCAAGATAACATCCATTTCCAAGACCACAGATAACTATATTATTAATCTTTACCACAAATATGGAGAGGGGTCGTGAGCGCGGGAGATCAAGGAATTCACGTGCCGACCAGCAAAATTCAACAGGTCCTCAATTTAGGACAAGATCCATTTCCCGGGATAAGACAACAACAGACTACCGTAGTAGTCGAAGTACTTCGCAAGTTAGAGTCCCTACGGTTTTCCATAAGAAAGGTACTGGGACCCTTACTGTCCCTCCAGCACCTAAGGATGTTTGTCCTACTCTCAGAAAAGGATTTCTATGTGATAGTAATTTCTGTAAAAAGGACCATCAACTTGAAAGCCTAACCGACCGGGAGCTCCTACTTCTTATAGCACGGAAGACCTGTGGATCAACTGATTCATCGCTTAATATAGCTGCTCCTAAAGACCTAAGACTAGCAAATCCTACGGCTGATGACTTCAAGCAAGACGGCAGTCCAAAATTAACCCTAAAATTACTAGTCGAGACTGCTGAGTTTTGGGCCAATCAGAATATTAATGAAGTAGATGATGCAAAACTCCGTGCTCTCTTGACGTTGAGTGCTGTCTTAGTGCGGAAATTCTCTAAGTCACAGCTTAGTCAATTATGTGAGAGTCATCTTAGGAGGGAAAACTTAGGACAAGACCAAGCTGAATCAGTTCTCGAGGTTTATCAACGTTTACATAGTGACAAAGGAGGTGCTTTTGAGGCAGCACTATGGCAACAGTGGGATAGACAATCATTAACTATGTTTATATCTGCTTTCCTCCATGTAGCATTGCAACTTTCCTGTGAGAGCTCCACTGTAGTGATATCAGGCCTACGCTTACTTGCCCCCCCAAGCGTTAATGAAGGGCTCCCTCCTGCACCAGGGGAATATACTTGGTCAGAAGATAGTACAACTTAGCCTGTAGGGAGGACAAGTAAAACAAGATGCCCTTATCCTCTATAGATGGTATTTTTAGAGAGGGGGACAGGATAGGAATAAAGATAATGACTAAAGCCAATATAAAGATACGAACACAAGTAGAAATTAAAATAGAAATCAAAACAATCTCCCCTTATTCAATATGAAATATAATAGTGAGTATTTGTTTCATGATGTCAATCATTTATTGTTAAAAATAAACAAAGTCAGTAAGAGTGTTAGGATCGTTATATTGCAAGGATCCTCCCTAGAAGCGTTGAATCATCTCAAGTAGCCTAGAACAAGAACAGCAGAGCATTAAATTGAAATAGATAATAAGGATATTGCTTGTTTTTAAGATAGTTTTAGGAAGTTTAAAATTAAGAAAAAGAACCCATGGACACACTCTAGCATTGAGGATGGGGTTCCCTTGATGATAGTATAGTCTTAGGTATAGGGTAGTCCTACACGTACTATATTATACAGTCTAAACTTGTAAAATTAAACTACAAGAACATGATGAAAATTAATGAGAAGGTTCCAAGATTGACTTCAATCCAAACACCTTGCTCTGCCAATTTTCATCTCCTTAAGATATATGATTTTGTTCCTGCGAGATAAGGTTATCAAATAGGGTGTGTATCTCTTTTACATATTTGGGCTCCCACTAGGCTAGGGTTTATAGTTAAGGAAGACTCATCACATTTTTTATTGAACTAGTCTACTCGCAGAATCCTACCGGGAATAGAAATTAGAACATTTGTGATACTTTGACTATAGGAAATAATTTTCAACACTACCTGAGATCAGGTTATTCTTCCAACTTATTCTGCAAGTAATTGTTTAGCATCATAACAACAACGTTATAATTTAAGAATCAAGTCTTGTAACAGAAATAAAGATAACAGAAAGAACCTTTATTATACGGGTCCATTAATTTTATAGGAGAAGCTCCTTTTACAAGCCTAAGATTCCATTAGAGATAACCAGAATGGCTAAAGCCACAGGCCGGTACAACTTGGTAACACCAAAACGGGAGCTAGAGCAAGGAGTTGTGTTTAGCGACCTATGCAACTTCCTAGTGACTCCAACTGTGCAAGGATGGAAGGTTTACTGGGCTGGACTTGAGTTTGATGTCAACCAAAAGGGTATTACCCTGTTAAATCGTCTTAAAGTGAATGATTTTGCTCCTGCATGGGCGATGACCCGGAACCTCTTCCCACACTTGTTCAAAAACCAACAGTCTGAAGTCCAAACTCCCATTTGGGCCTTGAGGGTAATTCTTGCCGCCGGGATTCTTGACCAATTAATGGATCATTCCCTCATTGAGCCGCTATCAGGGGCCCTGAACCTAATTGCTGATTGGTTACTAACAACATCTACTAATCACTTCAACATGAGAACTCAACGAGTAAAGGACCAACTGAGCATGAGGATGTTATCTCTTATAAGGTCAAATATTATTAACTTTATAAATAAGCTCGAGACTCTTCATGTCGTTAATTACAAGGGACTTCTAAGCAGTGTTGAGATAGGAACACCAAGCTATGCAATCATCATTACCAGGACTAATATGGGTTATCTTGTCGAGGTTCAGGAACCAGATAAATCTGCGATGGATATACGACACCCTGGTCCTGTCAAATTCTCCTTACTACATGAATCGACACTTAAACCTGTTGCCACTCCTAAACCATCAAGCATTACTTCATTGATCATGGAGTTCAACAGTTCTTTGGCAATTTAATTGCCGTAATAAAAATTGTACGATAGGGCTAACATTGATTCCATAATCCATCGTAGGACAGAATCATTTTCCTGTATGATCTTAGTTTAATCTCTCTTTATACAATGATTAATAAGGAGCCTGTTTAAAATGTTACAAAAGTATACTGTTTGAACCCCTAGTATCCCTGTAAATATCCTCATTCAATTTTTTGCTTTTACATGTGTAGTCACCTGTATAGCATGACCCTAGTCATGCCTTTAATTAATACTTAATCTAACAGTTAATATAATGTATAACTTTCCATGTTCAAAGAGTAGTCAAAACAATGTGAGATCCAGTTTCACTCACAGCATCTATTCACTATTTACAGTATGATGAGCCCAAATTAACACGGTAGAGGTCTAGATTTATTAATAGAACGAGGAAGATTAAGAAAAAGTCCATAATGCTGGGGAGGCAATCCTTGCCACCATAGGACTTTTTCAATTCCTCTATTTTATGATGGCTACCCAACATACACAATATCCTGATGCAAGATTGTCTTCCCCAATTGTCTTAGACCAATGTGACCTAGTGACAAGAGCATGTGGACTTTACTCTGAGTATTCGCTGAACCCTAAACTAAAGACATGCCGTTTACCGAAACATATCTATAGATTAAAATATGACACTATTGTTTTACGATTTATTAGTGATGTCCCTGTAGCTACAATCCCAATAGACTACATTGCTCCGATGTTAATAAATGTTCTGGCAGATAGTAAAAATGTACCATTGGAACCTCCCTGCTTGAGTTTCTTGGATGAAATAGTCAATTATACCGTGCAGGATGCAGCCTTCCTTAATTATTACATGAATCAGATTAAAACACAGGAAGGAGTAATTACAGATCAATTAAAACAGAACATTCGTAGGGTCATTCACAAAAACAGATATCTATCTGCTCTATTCTTCTGGCATGATCTTGCCATCCTCACCCGTCGAGGGAGAATGAACCGAGGAAATGTGCGCTCCACTTGGTTTGTAACGAATGAGGTTGTTGACATTCTAGGATATGGTGATTATATCTTCTGGAAGATCCCTATTGCTCTATTACCAATGAACACAGCTAATGTTCCACATGCATCAACTGACTGGTACCAACCTAATATCTTCAAGGAGGCTATTCAAGGACACACACATATTATTTCAGTCTCTACAGCCGAGGTCCTTATTATGTGTAAGGATCTTGTCACAAGTCGTTTTAATACCCTTCTGATTGCTGAGTTAGCCAGGTTGGAAGATCCAGTGTCTGCTGATTATCCACTAGTAGATAATATTCAATCTCTGTATAACGCAGGAGACTACCTGTTGTCCATATTGGGATCAGAGGGGTACAAAATAATCAAATATCTCGAACCTCTGTGTTTGGCTAAGATTCAACTATGTTCCCAATATACAGAACGAAAAGGGCGGTTTTTAACCCAGATGCATCTTGCAGTTATTCAGACATTGCGTGAACTCCTCCTTAATAGAGGGTTGAAAAAATCACAATTGTCTAAAATCCGCGAGTTTCACCAACTGTTGCTCAGACTCCGATCTACACCACAACAATTATGTGAATTATTTTCAATCCAAAAACACTGGGGCCACCCAGTTCTGCATAGTGAAAAGGCCATCCAAAAGGTTAAAAATCATGCAACAGTTCTAAAGGCATTGCGGCCGATTATCATCTTTGAAACGTATTGTGTATTCAAGTATAGTGTTGCAAAACATTTCTTTGATAGTCAAGGCACTTGGTACAGTGTGATATCAGACCGATGTTTAACGCCGGGATTGAATTCCTACATTAGGCGAAATCAATTCCCTCCACTTCCAATGATCAAAGATCTTTTATGGGAATTTTACCATTTGGATCATCCTCCATTATTCTCCACGAAGATCATTAGTGACCTCAGCATTTTCATTAAAGACCGCGCAACAGCAGTTGAACAAACCTGTTGGGATGCAGTTTTTGAGCCTAACGTTTTGGGCTACAGTCCACCTTATCGATTCAATACCAAACGTGTACCTGAACAATTCCTGGAGCAAGAGGATTTTTCTATTGAGAGTGTCTTACAATACGCCCAAGAACTTAGGTACTTATTGCCCCAGAATCGAAATTTTTCTTTTTCATTGAAGGAAAAAGAATTAAATGTTGGTAGGACATTTGGAAAATTGCCTTATTTAACCAGGAATGTCCAAACCCTCTGCGAAGCATTACTTGCAGATGGTTTGGCTAAAGCCTTTCCAAGCAATATGATGGTTGTCACAGAGAGGGAACAAAAGGAGAGCCTCCTTCACCAAGCATCCTGGCACCATACAAGTGATGATTTCGGAGAGCATGCCACAGTTCGTGGAAGTAGTTTTGTCACAGACCTGGAAAAATACAATCTGGCCTTCAGGTATGAATTCACAGCTCCCTTCATCAAATATTGCAACCAATGCTATGGGGTTCGCAATGTCTTTGATTGGATGCACTTCCTAATTCCGCAATGTTACATGCATGTTAGTGATTATTATAACCCACCACATAATGTAACCTTAGAGAATAGGGAATATCCCCCCGAAGGACCAAGTGCTTATAGAGGCCACCTTGGCGGTATTGAGGGGCTTCAACAAAAGTTATGGACTAGTATCTCATGTGCTCAAATCTCATTGGTAGAGATCAAGACCGGGTTCAAATTGCGATCAGCAGTCATGGGGGATAATCAATGTATTACAGTATTATCAGTCTTTCCACTAGAATCTAGTCCGAATGAGCAGGAGAGATGCGCAGAAGACAATGCAGCCAGAGTGGCTGCTAGCTTGGCCAAAGTCACAAGTGCCTGTGGGATATTCCTCAAGCCTGATGAGACTTTCGTACACTCAGGCTTTATCTATTTTGGCAAAAAGCAATACTTGAACGGAATTCAATTACCTCAATCACTCAAGACAGCAGCTAGGATGGCCCCTCTCTCAGATGCAATTTTTGATGACTTGCAAGGTACACTTGCCAGTATAGGAACTGCCTTTGAGCGATCAATCTCCGAAACTAGACATATTTTACCATGCCGTGTTGCAGCTGCCTTTCATACATATTTCTCTGTTCGGATCTTACAACATCATCACCTTGGTTTCCATAAGGGTTCAGACCTTGGACAATTGGCAATCAATAAACCTCTTGATTTCGGGACCATTGCACTATCCTTAGCAGTTCCTCAGGTATTGGGTGGATTATCCTTCCTAAATCCAGAAAAGTGCCTTTATCGCAACTTGGGTGATCCTGTAACTTCAGGCCTATTTCAGTTGAAGCATTATCTGTCAATGGTGGGTATGAGTGATATCTTTCATGCACTTATTGCAAAAAGCCCAGGGAATTGTAGCGCAATTGACTTTGTTCTAAACCCAGGCGGGTTAAATGTCCCTGGATCACAGGATTTAACATCTTTCCTTCGTCAGATTGTCAGAAGGAGTATCACACTTTCGGCAAGGAACAAGTTAATCAACACGTTATTTCACGCTTCTGCAGATCTTGAAGACGAATTAGTATGTAAATGGTTACTTTCTTCAACGCCCGTGATGAGCCGTTTTGCAGCCGATATTTTCTCACGAACACCAAGCGGGAAAAGATTACAAATCTTGGGATACCTCGAGGGAACCAGAACTTTATTAGCATCCAAAATGATAAGCAATAATGCAGAGACACCAATCTTGGAGAGGCTCAGAAAAATAACACTTCAAAGATGGAATCTATGGTTTAGTTACCTAGACCATTGTGACCCAGCTTTAATGGAAGCAATTCAACCAATTAAGTGTACTGTTGATATTGCTCAAATTCTTAGAGAATACTCCTGGGCTCATATCCTTGATGGTAGACAGTTAATAGGGGCAACACTGCCATGTATACCTGAGCAGTTCCAAACCACATGGTTAAAACCTTACGAGCAATGTGTGGAATGTTCATCCACAAACAATTCTAGTCCATATGTATCAGTTGCATTAAAAAGGAACGTGGTTAGTGCTTGGCCTGATGCATCTAGATTGGGGTGGACGATTGGTGATGGGATTCCCTACATAGGCTCAAGAACTGAGGACAAAATAGGTCAGCCCGCTATTAAGCCGAGGTGCCCATCAGCTGCATTAAGAGAAGCTATTGAATTGACCTCTAGGTTGACCTGGGTCACTCAAGGTAGTGCAAACAGCGATCAGTTAATTCGCCCTTTTCTTGAGGCAAGAGTAAACTTGAGTGTACAAGAGATTCTTCAAATGACCCCCTCACATTACTCCGGTAATATTGTGCATCGGTATAATGATCAGTATAGCCCTCACTCCTTTATGGCTAACCGCATGAGTAACACAGCAACGCGCTTGATGGTATCTACCAACACACTAGGAGAGTTTTCCGGAGGGGGTCAGGCTGCACGTGATAGCAACATTATATTTCAAAATGTGATTAACTTTGCAGTGGCCTTGTATGACATTAGGTTTCGGAACACTTGTACATCTTCTATTCAATATCACAGGGCCCATATTCACCTGACGAATTGTTGTACGAGGGAAGTACCGGCCCAATACTTAACATACACAACCACGCTAAATCTAGATTTGAGTAAGTACCGTAATAATGAACTGATTTATGATTCAGATCCACTAAGAGGAGGTCTCAACTGCAACTTATCGATTGACAGTCCTTTGATGAAGGGCCCACGTTTAAATATTATTGAGGATGACTTAATACGGTTGCCACATTTATCCGGCTGGGAATTAGCAAAAACAGTCTTGCAATCAATAATCTCTGATAGTAGCAATTCATCAACAGATCCCATTAGCAGCGGTGAAACAAGATCCTTCACAACCCACTTCTTAACGTATCCCAAAATAGGGCTTCTATACAGTTTTGGAGCCCTCATAAGTTTTTATTTGGGTAATACTATTCTATGCACGAAAAAGATCGGACTCACAGAATTTCTATACTATCTCCAGAATCAGATCCACAACTTATCACATAGATCCCTTCGAATCTTCAAACCGACATTTAGACACTCAAGTGTCATGTCCAGGTTGATGGATATAGACCCCAACTTCTCAATATATATTGGTGGGACTGCAGGTGACCGTGGATTATCGGACGCTGCAAGATTATTTCTCCGAATTGCAATTTCAACTTTCTTGAGCTTTGTTGAGGAGTGGGTTATCTTTAGGAAGGCAAACATCCCACTATGGGTTATCTATCCTCTCGAAGGCCAACGCTCTGATCCTCCTGGCGAATTTTTGAACCGAGTAAAATCTCTAATTGTTGGGACTGAAGATGATAAAAATAAAGGCTCTATACTTTCAAGATCTGGAGAGAAATGCTCTTCAAATCTAGTTTATAATTGCAAGAGTACAGCAAGCAATTTTTTCCATGCATCATTGGCTTACTGGAGAGGTCGACATAGACCTAAGAAGACTATAGGTGCAACTAACGCGACAACAGCTCCACATATCATTTTGCCACTGGGAAATTCTGATCGACCGCCTGGCCTAGACCTTAATAGGAACAATGATACTTTCATTCCTACCAGAATTAAACAGATAGTCCAAGGAGACTCTAGAAACGACAGAACGACCACCACGAGATTTCCACCCAAAAGTAGGTCCACTCCAACATCAGCAACCGAGCCTCCTACAAAAATGTATGAGGGTTCGACAACCCACCAAGGGAAATTAACAGATACACATTTGGATGAGGATCACAATGCCAAAGAGTTCCCATCCAATCCGCATCGTTTAGTAGTACCATTCTTTAAATTAACAAAAGATGGGGAATACAGCATCGAACCTTCTCCTGAAGAAAGCCGCAGTAATATAAAAGGGTTACTTCAACATTTAAGAACCATGGTTGATACTACCATATATTGTCGCTTCACTGGAATTGTTTCATCAATGCATTATAAGTTAGATGAAGTACTATGGGAATATAATAAATTTGAATCAGCTGTAACCCTAGCAGAAGGGGAGGGTTCAGGTGCCTTACTACTGATCCAAAAATACGGCGTTAAGAAGTTATTTTTGAATACACTTGCTACTGAACATAGTATTGAGAGTGAAGTGATATCAGGTTACACCACTCCAAGGATGCTACTCCCAATTATGCCTAAAACACATCGTGGTGAGCTAGAGGTCATATTAAATAACTCAGCTAGTCAAATAACTGATATTACACATCGAGATTGGTTTTCAAATCAAAAAAATAGGATTCCAAATGATGCTGATATTATTACCATGGATGCTGAAACTACAGAAAACTTAGATCGTTCCAGATTATATGAAGCAGTATATACGATTATTTGTAATCATATCAATCCTAAAACTTTGAAAGTGGTCATCTTAAAAGTCTTCCTCAGCGATTTGGATGGGATGTGCTGGATTAACAATTATCTTGCTCCTATGTTTGGATCAGGATATTTAATCAAACCTATAACATCAAGTGCAAAGTCAAGTGAGTGGTATTTATGCTTATCTAATCTACTTTCAACCTTGAGAACTACTCAGCATCAAACCCAGGCAAACTGTCTCCATGTCGTACAATGTGCTCTTCAACAGCAAGTACAAAGAGGGTCATATTGGCTAAGTCATCTTACCAAATACACCACAAGTAGATTGCACAATAGTTATATTGCATTTGGTTTTCCTTCATTAGAGAAGGTCCTATATCATAGGTATAACCTTGTTGATTCGAGAAATGGACCATTAGTTTCTATAACGAGACACCTTGCCCTCCTCCAAACTGAGATCCGGGAGTTGGTAACTGATTATAATCAGCTGCGACAAAGTCGAACCCAGACTTATCATTTCATAAAAACATCCAAGGGACGGATAACTAAACTAGTGAATGATTATCTAAGATTTGAGTTGGTTATACGGGCTCTTAAAAATAATTCTACATGGCACCATGAGTTATACTTGCTACCAGAACTTATAGGTGTTTGCCATCGATTTAATCATACACGTAACTGTACATGCAGTGAAAGGTTCCTGGTTCAAACTTTATATCTACACCGAATGAGTGATGCTGAGATAAAACTTATGGACCGGCTCACCAGCCTAGTCAATATGTTTCCTGAAGGTTTCAGGTCTAGTTCAGTCTAATTCTAACTGCACCAAAGGCTCTAAAAATATTTTAAATAACCAGGTGTATATCAAAGTCAATACAAGTGTAAAAACAATATGCAAGGGACCACATTTAGGATCAGTTTATTGACTCTTCCAATACACAGAGTTGGAAGCACCGATTCAAGGTTTCTAAGACGCCCTATCGATTATGTTGATAATGTAAATAATAGCTTTTCCTGTCTATTATGACTTAAATAATCATATCTATAACGACCATCACAGCTAAGTCGTTGCCCTAGTTCATATATTAAATTAAAATTTAGAAGCTAGGTTGACTCTAATTACATAAGTATTAAGAAAAAATTACTAAGACTAATACTCTCATGCCAAGAACTAGTAATGTGTTTCACATGACAGATTATTTCTAACACTAAATTGCAATTTCAATTTTAAAGCTAAGTTTAACACCTATACAGCCAAAATATTTCATAGGGCCGATGGGAATAACATAAGAGGAACATGATCAATGAACCCTTTATTCCAACTAGGCAGTTGATTGATAATCTACAAATTCCATAAGATGTTCTTACGATATTCTTTTGTTTTTAATCTCAATGTCAATGATTTAATAAGTAATAATAAAAAAATCACATTAAAGATGCAGGAAGATCTTGACCTCGCCAGGAAAATTAAGCGCACACAAATAAATTAAAAAATCTGTATTTTCTCTTTTTTGTGTGTCCA diff --git a/preprocessing/nextclade/tests/ebola-multipath-dataset/ebola-zaire/genome_annotation.gff3 b/preprocessing/nextclade/tests/ebola-multipath-dataset/ebola-zaire/genome_annotation.gff3 new file mode 100644 index 0000000000..fa9fc3d1f9 --- /dev/null +++ b/preprocessing/nextclade/tests/ebola-multipath-dataset/ebola-zaire/genome_annotation.gff3 @@ -0,0 +1,19 @@ +##gff-version 3 +#!gff-spec-version 1.21 +#!processor NCBI annotwriter +##sequence-region NC_002549.1 1 18959 +##species https://www.ncbi.nlm.nih.gov/Taxonomy/Browser/wwwtax.cgi?id=186538 +NC_002549.1 RefSeq gene 9885 11518 . + . ID=gene-ZEBOVgp6;Dbxref=GeneID:911828;Name=VP24EbolaZaire;Note=putative;gbkey=Gene;gene=VP24EbolaZaire;gene_biotype=protein_coding;locus_tag=ZEBOVgp6 +NC_002549.1 RefSeq mRNA 9885 11496 . + . ID=rna-ZEBOVgp6;Parent=gene-ZEBOVgp6;Dbxref=GeneID:911828;gbkey=mRNA;gene=VP24EbolaZaire;locus_tag=ZEBOVgp6;product=VP24 +NC_002549.1 RefSeq exon 9885 11496 . + . ID=exon-ZEBOVgp6-1;Parent=rna-ZEBOVgp6;Dbxref=GeneID:911828;gbkey=mRNA;gene=VP24EbolaZaire;locus_tag=ZEBOVgp6;product=VP24 +NC_002549.1 RefSeq CDS 10345 11100 . + 0 ID=cds-NP_066250.1;Parent=rna-ZEBOVgp6;Dbxref=GenBank:NP_066250.1,GeneID:911828;Name=VP24EbolaZaire;gbkey=CDS;gene=VP24EbolaZaire;locus_tag=ZEBOVgp6;product=membrane-associated protein;protein_id=NP_066250.1 +NC_002549.1 RefSeq sequence_feature 11497 11500 . + . ID=id-ZEBOVgp6;Dbxref=GeneID:911828;Note=intergenic region;gbkey=misc_feature;gene=VP24EbolaZaire;locus_tag=ZEBOVgp6 +NC_002549.1 RefSeq regulatory_region 9885 9896 . + . ID=id-ZEBOVgp6-2;Parent=gene-ZEBOVgp6;Dbxref=GeneID:911828;Note=transcription start signal;gbkey=regulatory;gene=VP24EbolaZaire;locus_tag=ZEBOVgp6;regulatory_class=other +NC_002549.1 RefSeq polyA_signal_sequence 11485 11496 . + . ID=id-ZEBOVgp6-3;Parent=gene-ZEBOVgp6;Dbxref=GeneID:911828;Note=putative;gbkey=regulatory;gene=VP24EbolaZaire;locus_tag=ZEBOVgp6;regulatory_class=polyA_signal_sequence +NC_002549.1 RefSeq polyA_signal_sequence 11508 11518 . + . ID=id-ZEBOVgp6-4;Parent=gene-ZEBOVgp6;Dbxref=GeneID:911828;gbkey=regulatory;gene=VP24EbolaZaire;locus_tag=ZEBOVgp6;regulatory_class=polyA_signal_sequence +NC_002549.1 RefSeq gene 11501 18282 . + . ID=gene-ZEBOVgp7;Dbxref=GeneID:911824;Name=LEbolaZaire;gbkey=Gene;gene=LEbolaZaire;gene_biotype=protein_coding;locus_tag=ZEBOVgp7 +NC_002549.1 RefSeq mRNA 11501 18282 . + . ID=rna-ZEBOVgp7;Parent=gene-ZEBOVgp7;Dbxref=GeneID:911824;gbkey=mRNA;gene=LEbolaZaire;locus_tag=ZEBOVgp7;product=polymerase +NC_002549.1 RefSeq exon 11501 18282 . + . ID=exon-ZEBOVgp7-1;Parent=rna-ZEBOVgp7;Dbxref=GeneID:911824;gbkey=mRNA;gene=LEbolaZaire;locus_tag=ZEBOVgp7;product=polymerase +NC_002549.1 RefSeq CDS 11581 18219 . + 0 ID=cds-NP_066251.1;Parent=rna-ZEBOVgp7;Dbxref=GenBank:NP_066251.1,GeneID:911824;Name=LEbolaZaire;gbkey=CDS;gene=LEbolaZaire;locus_tag=ZEBOVgp7;product=RNA-dependent RNA polymerase;protein_id=NP_066251.1 +NC_002549.1 RefSeq regulatory_region 11501 11512 . + . ID=id-ZEBOVgp7;Parent=gene-ZEBOVgp7;Dbxref=GeneID:911824;Note=transcription start signal;gbkey=regulatory;gene=LEbolaZaire;locus_tag=ZEBOVgp7;regulatory_class=other +NC_002549.1 RefSeq polyA_signal_sequence 18272 18282 . + . ID=id-ZEBOVgp7-2;Parent=gene-ZEBOVgp7;Dbxref=GeneID:911824;gbkey=regulatory;gene=LEbolaZaire;locus_tag=ZEBOVgp7;regulatory_class=polyA_signal_sequence diff --git a/preprocessing/nextclade/tests/ebola-multipath-dataset/ebola-zaire/pathogen.json b/preprocessing/nextclade/tests/ebola-multipath-dataset/ebola-zaire/pathogen.json new file mode 100644 index 0000000000..462de6208e --- /dev/null +++ b/preprocessing/nextclade/tests/ebola-multipath-dataset/ebola-zaire/pathogen.json @@ -0,0 +1,24 @@ +{ + "attributes": { + "name": "Ebolavirus Zaire", + "reference accession": "NC_002549.1", + "reference name": "COD/1976/Yambuku-Mayinga" + }, + "compatibility": { + "cli": "3.0.0-alpha.0", + "web": "3.0.0-alpha.0" + }, + "deprecated": false, + "enabled": true, + "experimental": true, + "files": { + "genomeAnnotation": "genome_annotation.gff3", + "pathogenJson": "pathogen.json", + "reference": "reference.fasta" + }, + "official": false, + "qc": {}, + "schemaVersion": "3.0.0", + "shortcuts": [ + ] +} diff --git a/preprocessing/nextclade/tests/ebola-multipath-dataset/ebola-zaire/reference.fasta b/preprocessing/nextclade/tests/ebola-multipath-dataset/ebola-zaire/reference.fasta new file mode 100644 index 0000000000..198de84758 --- /dev/null +++ b/preprocessing/nextclade/tests/ebola-multipath-dataset/ebola-zaire/reference.fasta @@ -0,0 +1,2 @@ +>NC_002549.1 Zaire ebolavirus isolate Ebola virus/H.sapiens-tc/COD/1976/Yambuku-Mayinga +CGGACACACAAAAAGAAAGAAGAATTTTTAGGATCTTTTGTGTGCGAATAACTATGAGGAAGATTAATAATTTTCCTCTCATTGAAATTTATATCGGAATTTAAATTGAAATTGTTACTGTAATCACACCTGGTTTGTTTCAGAGCCACATCACAAAGATAGAGAACAACCTAGGTCTCCGAAGGGAGCAAGGGCATCAGTGTGCTCAGTTGAAAATCCCTTGTCAACACCTAGGTCTTATCACATCACAAGTTCCACCTCAGACTCTGCAGGGTGATCCAACAACCTTAATAGAAACATTATTGTTAAAGGACAGCATTAGTTCACAGTCAAACAAGCAAGATTGAGAATTAACCTTGGTTTTGAACTTGAACACTTAGGGGATTGAAGATTCAACAACCCTAAAGCTTGGGGTAAAACATTGGAAATAGTTAAAAGACAAATTGCTCGGAATCACAAAATTCCGAGTATGGATTCTCGTCCTCAGAAAATCTGGATGGCGCCGAGTCTCACTGAATCTGACATGGATTACCACAAGATCTTGACAGCAGGTCTGTCCGTTCAACAGGGGATTGTTCGGCAAAGAGTCATCCCAGTGTATCAAGTAAACAATCTTGAAGAAATTTGCCAACTTATCATACAGGCCTTTGAAGCAGGTGTTGATTTTCAAGAGAGTGCGGACAGTTTCCTTCTCATGCTTTGTCTTCATCATGCGTACCAGGGAGATTACAAACTTTTCTTGGAAAGTGGCGCAGTCAAGTATTTGGAAGGGCACGGGTTCCGTTTTGAAGTCAAGAAGCGTGATGGAGTGAAGCGCCTTGAGGAATTGCTGCCAGCAGTATCTAGTGGAAAAAACATTAAGAGAACACTTGCTGCCATGCCGGAAGAGGAGACAACTGAAGCTAATGCCGGTCAGTTTCTCTCCTTTGCAAGTCTATTCCTTCCGAAATTGGTAGTAGGAGAAAAGGCTTGCCTTGAGAAGGTTCAAAGGCAAATTCAAGTACATGCAGAGCAAGGACTGATACAATATCCAACAGCTTGGCAATCAGTAGGACACATGATGGTGATTTTCCGTTTGATGCGAACAAATTTTCTGATCAAATTTCTCCTAATACACCAAGGGATGCACATGGTTGCCGGGCATGATGCCAACGATGCTGTGATTTCAAATTCAGTGGCTCAAGCTCGTTTTTCAGGCTTATTGATTGTCAAAACAGTACTTGATCATATCCTACAAAAGACAGAACGAGGAGTTCGTCTCCATCCTCTTGCAAGGACCGCCAAGGTAAAAAATGAGGTGAACTCCTTTAAGGCTGCACTCAGCTCCCTGGCCAAGCATGGAGAGTATGCTCCTTTCGCCCGACTTTTGAACCTTTCTGGAGTAAATAATCTTGAGCATGGTCTTTTCCCTCAACTATCGGCAATTGCACTCGGAGTCGCCACAGCACACGGGAGTACCCTCGCAGGAGTAAATGTTGGAGAACAGTATCAACAACTCAGAGAGGCTGCCACTGAGGCTGAGAAGCAACTCCAACAATATGCAGAGTCTCGCGAACTTGACCATCTTGGACTTGATGATCAGGAAAAGAAAATTCTTATGAACTTCCATCAGAAAAAGAACGAAATCAGCTTCCAGCAAACAAACGCTATGGTAACTCTAAGAAAAGAGCGCCTGGCCAAGCTGACAGAAGCTATCACTGCTGCGTCACTGCCCAAAACAAGTGGACATTACGATGATGATGACGACATTCCCTTTCCAGGACCCATCAATGATGACGACAATCCTGGCCATCAAGATGATGATCCGACTGACTCACAGGATACGACCATTCCCGATGTGGTGGTTGATCCCGATGATGGAAGCTACGGCGAATACCAGAGTTACTCGGAAAACGGCATGAATGCACCAGATGACTTGGTCCTATTCGATCTAGACGAGGACGACGAGGACACTAAGCCAGTGCCTAATAGATCGACCAAGGGTGGACAACAGAAGAACAGTCAAAAGGGCCAGCATATAGAGGGCAGACAGACACAATCCAGGCCAATTCAAAATGTCCCAGGCCCTCACAGAACAATCCACCACGCCAGTGCGCCACTCACGGACAATGACAGAAGAAATGAACCCTCCGGCTCAACCAGCCCTCGCATGCTGACACCAATTAACGAAGAGGCAGACCCACTGGACGATGCCGACGACGAGACGTCTAGCCTTCCGCCCTTGGAGTCAGATGATGAAGAGCAGGACAGGGACGGAACTTCCAACCGCACACCCACTGTCGCCCCACCGGCTCCCGTATACAGAGATCACTCTGAAAAGAAAGAACTCCCGCAAGACGAGCAACAAGATCAGGACCACACTCAAGAGGCCAGGAACCAGGACAGTGACAACACCCAGTCAGAACACTCTTTTGAGGAGATGTATCGCCACATTCTAAGATCACAGGGGCCATTTGATGCTGTTTTGTATTATCATATGATGAAGGATGAGCCTGTAGTTTTCAGTACCAGTGATGGCAAAGAGTACACGTATCCAGACTCCCTTGAAGAGGAATATCCACCATGGCTCACTGAAAAAGAGGCTATGAATGAAGAGAATAGATTTGTTACATTGGATGGTCAACAATTTTATTGGCCGGTGATGAATCACAAGAATAAATTCATGGCAATCCTGCAACATCATCAGTGAATGAGCATGGAACAATGGGATGATTCAACCGACAAATAGCTAACATTAAGTAGTCAAGGAACGAAAACAGGAAGAATTTTTGATGTCTAAGGTGTGAATTATTATCACAATAAAAGTGATTCTTATTTTTGAATTTAAAGCTAGCTTATTATTACTAGCCGTTTTTCAAAGTTCAATTTGAGTCTTAATGCAAATAGGCGTTAAGCCACAGTTATAGCCATAATTGTAACTCAATATTCTAACTAGCGATTTATCTAAATTAAATTACATTATGCTTTTATAACTTACCTACTAGCCTGCCCAACATTTACACGATCGTTTTATAATTAAGAAAAAACTAATGATGAAGATTAAAACCTTCATCATCCTTACGTCAATTGAATTCTCTAGCACTCGAAGCTTATTGTCTTCAATGTAAAAGAAAAGCTGGTCTAACAAGATGACAACTAGAACAAAGGGCAGGGGCCATACTGCGGCCACGACTCAAAACGACAGAATGCCAGGCCCTGAGCTTTCGGGCTGGATCTCTGAGCAGCTAATGACCGGAAGAATTCCTGTAAGCGACATCTTCTGTGATATTGAGAACAATCCAGGATTATGCTACGCATCCCAAATGCAACAAACGAAGCCAAACCCGAAGACGCGCAACAGTCAAACCCAAACGGACCCAATTTGCAATCATAGTTTTGAGGAGGTAGTACAAACATTGGCTTCATTGGCTACTGTTGTGCAACAACAAACCATCGCATCAGAATCATTAGAACAACGCATTACGAGTCTTGAGAATGGTCTAAAGCCAGTTTATGATATGGCAAAAACAATCTCCTCATTGAACAGGGTTTGTGCTGAGATGGTTGCAAAATATGATCTTCTGGTGATGACAACCGGTCGGGCAACAGCAACCGCTGCGGCAACTGAGGCTTATTGGGCCGAACATGGTCAACCACCACCTGGACCATCACTTTATGAAGAAAGTGCGATTCGGGGTAAGATTGAATCTAGAGATGAGACCGTCCCTCAAAGTGTTAGGGAGGCATTCAACAATCTAAACAGTACCACTTCACTAACTGAGGAAAATTTTGGGAAACCTGACATTTCGGCAAAGGATTTGAGAAACATTATGTATGATCACTTGCCTGGTTTTGGAACTGCTTTCCACCAATTAGTACAAGTGATTTGTAAATTGGGAAAAGATAGCAACTCATTGGACATCATTCATGCTGAGTTCCAGGCCAGCCTGGCTGAAGGAGACTCTCCTCAATGTGCCCTAATTCAAATTACAAAAAGAGTTCCAATCTTCCAAGATGCTGCTCCACCTGTCATCCACATCCGCTCTCGAGGTGACATTCCCCGAGCTTGCCAGAAAAGCTTGCGTCCAGTCCCACCATCGCCCAAGATTGATCGAGGTTGGGTATGTGTTTTTCAGCTTCAAGATGGTAAAACACTTGGACTCAAAATTTGAGCCAATCTCCCTTCCCTCCGAAAGAGGCGAATAATAGCAGAGGCTTCAACTGCTGAACTATAGGGTACGTTACATTAATGATACACTTGTGAGTATCAGCCCTGGATAATATAAGTCAATTAAACGACCAAGATAAAATTGTTCATATCTCGCTAGCAGCTTAAAATATAAATGTAATAGGAGCTATATCTCTGACAGTATTATAATCAATTGTTATTAAGTAACCCAAACCAAAAGTGATGAAGATTAAGAAAAACCTACCTCGGCTGAGAGAGTGTTTTTTCATTAACCTTCATCTTGTAAACGTTGAGCAAAATTGTTAAAAATATGAGGCGGGTTATATTGCCTACTGCTCCTCCTGAATATATGGAGGCCATATACCCTGTCAGGTCAAATTCAACAATTGCTAGAGGTGGCAACAGCAATACAGGCTTCCTGACACCGGAGTCAGTCAATGGGGACACTCCATCGAATCCACTCAGGCCAATTGCCGATGACACCATCGACCATGCCAGCCACACACCAGGCAGTGTGTCATCAGCATTCATCCTTGAAGCTATGGTGAATGTCATATCGGGCCCCAAAGTGCTAATGAAGCAAATTCCAATTTGGCTTCCTCTAGGTGTCGCTGATCAAAAGACCTACAGCTTTGACTCAACTACGGCCGCCATCATGCTTGCTTCATACACTATCACCCATTTCGGCAAGGCAACCAATCCACTTGTCAGAGTCAATCGGCTGGGTCCTGGAATCCCGGATCATCCCCTCAGGCTCCTGCGAATTGGAAACCAGGCTTTCCTCCAGGAGTTCGTTCTTCCGCCAGTCCAACTACCCCAGTATTTCACCTTTGATTTGACAGCACTCAAACTGATCACCCAACCACTGCCTGCTGCAACATGGACCGATGACACTCCAACAGGATCAAATGGAGCGTTGCGTCCAGGAATTTCATTTCATCCAAAACTTCGCCCCATTCTTTTACCCAACAAAAGTGGGAAGAAGGGGAACAGTGCCGATCTAACATCTCCGGAGAAAATCCAAGCAATAATGACTTCACTCCAGGACTTTAAGATCGTTCCAATTGATCCAACCAAAAATATCATGGGAATCGAAGTGCCAGAAACTCTGGTCCACAAGCTGACCGGTAAGAAGGTGACTTCTAAAAATGGACAACCAATCATCCCTGTTCTTTTGCCAAAGTACATTGGGTTGGACCCGGTGGCTCCAGGAGACCTCACCATGGTAATCACACAGGATTGTGACACGTGTCATTCTCCTGCAAGTCTTCCAGCTGTGATTGAGAAGTAATTGCAATAATTGACTCAGATCCAGTTTTATAGAATCTTCTCAGGGATAGTGATAACATCTATTTAGTAATCCGTCCATTAGAGGAGACACTTTTAATTGATCAATATACTAAAGGTGCTTTACACCATTGTCTTTTTTCTCTCCTAAATGTAGAACTTAACAAAAGACTCATAATATACTTGTTTTTAAAGGATTGATTGATGAAAGATCATAACTAATAACATTACAAATAATCCTACTATAATCAATACGGTGATTCAAATGTTAATCTTTCTCATTGCACATACTTTTTGCCCTTATCCTCAAATTGCCTGCATGCTTACATCTGAGGATAGCCAGTGTGACTTGGATTGGAAATGTGGAGAAAAAATCGGGACCCATTTCTAGGTTGTTCACAATCCAAGTACAGACATTGCCCTTCTAATTAAGAAAAAATCGGCGATGAAGATTAAGCCGACAGTGAGCGTAATCTTCATCTCTCTTAGATTATTTGTTTTCCAGAGTAGGGGTCGTCAGGTCCTTTTCAATCGTGTAACCAAAATAAACTCCACTAGAAGGATATTGTGGGGCAACAACACAATGGGCGTTACAGGAATATTGCAGTTACCTCGTGATCGATTCAAGAGGACATCATTCTTTCTTTGGGTAATTATCCTTTTCCAAAGAACATTTTCCATCCCACTTGGAGTCATCCACAATAGCACATTACAGGTTAGTGATGTCGACAAACTAGTTTGTCGTGACAAACTGTCATCCACAAATCAATTGAGATCAGTTGGACTGAATCTCGAAGGGAATGGAGTGGCAACTGACGTGCCATCTGCAACTAAAAGATGGGGCTTCAGGTCCGGTGTCCCACCAAAGGTGGTCAATTATGAAGCTGGTGAATGGGCTGAAAACTGCTACAATCTTGAAATCAAAAAACCTGACGGGAGTGAGTGTCTACCAGCAGCGCCAGACGGGATTCGGGGCTTCCCCCGGTGCCGGTATGTGCACAAAGTATCAGGAACGGGACCGTGTGCCGGAGACTTTGCCTTCCATAAAGAGGGTGCTTTCTTCCTGTATGATCGACTTGCTTCCACAGTTATCTACCGAGGAACGACTTTCGCTGAAGGTGTCGTTGCATTTCTGATACTGCCCCAAGCTAAGAAGGACTTCTTCAGCTCACACCCCTTGAGAGAGCCGGTCAATGCAACGGAGGACCCGTCTAGTGGCTACTATTCTACCACAATTAGATATCAGGCTACCGGTTTTGGAACCAATGAGACAGAGTACTTGTTCGAGGTTGACAATTTGACCTACGTCCAACTTGAATCAAGATTCACACCACAGTTTCTGCTCCAGCTGAATGAGACAATATATACAAGTGGGAAAAGGAGCAATACCACGGGAAAACTAATTTGGAAGGTCAACCCCGAAATTGATACAACAATCGGGGAGTGGGCCTTCTGGGAAACTAAAAAAACCTCACTAGAAAAATTCGCAGTGAAGAGTTGTCTTTCACAGTTGTATCAAACGGAGCCAAAAACATCAGTGGTCAGAGTCCGGCGCGAACTTCTTCCGACCCAGGGACCAACACAACAACTGAAGACCACAAAATCATGGCTTCAGAAAATTCCTCTGCAATGGTTCAAGTGCACAGTCAAGGAAGGGAAGCTGCAGTGTCGCATCTAACAACCCTTGCCACAATCTCCACGAGTCCCCAATCCCTCACAACCAAACCAGGTCCGGACAACAGCACCCATAATACACCCGTGTATAAACTTGACATCTCTGAGGCAACTCAAGTTGAACAACATCACCGCAGAACAGACAACGACAGCACAGCCTCCGACACTCCCTCTGCCACGACCGCAGCCGGACCCCCAAAAGCAGAGAACACCAACACGAGCAAGAGCACTGACTTCCTGGACCCCGCCACCACAACAAGTCCCCAAAACCACAGCGAGACCGCTGGCAACAACAACACTCATCACCAAGATACCGGAGAAGAGAGTGCCAGCAGCGGGAAGCTAGGCTTAATTACCAATACTATTGCTGGAGTCGCAGGACTGATCACAGGCGGGAGAAGAACTCGAAGAGAAGCAATTGTCAATGCTCAACCCAAATGCAACCCTAATTTACATTACTGGACTACTCAGGATGAAGGTGCTGCAATCGGACTGGCCTGGATACCATATTTCGGGCCAGCAGCCGAGGGAATTTACATAGAGGGGCTAATGCACAATCAAGATGGTTTAATCTGTGGGTTGAGACAGCTGGCCAACGAGACGACTCAAGCTCTTCAACTGTTCCTGAGAGCCACAACTGAGCTACGCACCTTTTCAATCCTCAACCGTAAGGCAATTGATTTCTTGCTGCAGCGATGGGGCGGCACATGCCACATTCTGGGACCGGACTGCTGTATCGAACCACATGATTGGACCAAGAACATAACAGACAAAATTGATCAGATTATTCATGATTTTGTTGATAAAACCCTTCCGGACCAGGGGGACAATGACAATTGGTGGACAGGATGGAGACAATGGATACCGGCAGGTATTGGAGTTACAGGCGTTATAATTGCAGTTATCGCTTTATTCTGTATATGCAAATTTGTCTTTTAGTTTTTCTTCAGATTGCTTCATGGAAAAGCTCAGCCTCAAATCAATGAAACCAGGATTTAATTATATGGATTACTTGAATCTAAGATTACTTGACAAATGATAATATAATACACTGGAGCTTTAAACATAGCCAATGTGATTCTAACTCCTTTAAACTCACAGTTAATCATAAACAAGGTTTGACATCAATCTAGTTATCTCTTTGAGAATGATAAACTTGATGAAGATTAAGAAAAAGGTAATCTTTCGATTATCTTTAATCTTCATCCTTGATTCTACAATCATGACAGTTGTCTTTAGTGACAAGGGAAAGAAGCCTTTTTATTAAGTTGTAATAATCAGATCTGCGAACCGGTAGAGTTTAGTTGCAACCTAACACACATAAAGCATTGGTCAAAAAGTCAATAGAAATTTAAACAGTGAGTGGAGACAACTTTTAAATGGAAGCTTCATATGAGAGAGGACGCCCACGAGCTGCCAGACAGCATTCAAGGGATGGACACGACCACCATGTTCGAGCACGATCATCATCCAGAGAGAATTATCGAGGTGAGTACCGTCAATCAAGGAGCGCCTCACAAGTGCGCGTTCCTACTGTATTTCATAAGAAGAGAGTTGAACCATTAACAGTTCCTCCAGCACCTAAAGACATATGTCCGACCTTGAAAAAAGGATTTTTGTGTGACAGTAGTTTTTGCAAAAAAGATCACCAGTTGGAGAGTTTAACTGATAGGGAATTACTCCTACTAATCGCCCGTAAGACTTGTGGATCAGTAGAACAACAATTAAATATAACTGCACCCAAGGACTCGCGCTTAGCAAATCCAACGGCTGATGATTTCCAGCAAGAGGAAGGTCCAAAAATTACCTTGTTGACACTGATCAAGACGGCAGAACACTGGGCGAGACAAGACATCAGAACCATAGAGGATTCAAAATTAAGAGCATTGTTGACTCTATGTGCTGTGATGACGAGGAAATTCTCAAAATCCCAGCTGAGTCTTTTATGTGAGACACACCTAAGGCGCGAGGGGCTTGGGCAAGATCAGGCAGAACCCGTTCTCGAAGTATATCAACGATTACACAGTGATAAAGGAGGCAGTTTTGAAGCTGCACTATGGCAACAATGGGACCGACAATCCCTAATTATGTTTATCACTGCATTCTTGAATATTGCTCTCCAGTTACCGTGTGAAAGTTCTGCTGTCGTTGTTTCAGGGTTAAGAACATTGGTTCCTCAATCAGATAATGAGGAAGCTTCAACCAACCCGGGGACATGCTCATGGTCTGATGAGGGTACCCCTTAATAAGGCTGACTAAAACACTATATAACCTTCTACTTGATCACAATACTCCGTATACCTATCATCATATATTTAATCAAGACGATATCCTTTAAAACTTATTCAGTACTATAATCACTCTCGTTTCAAATTAATAAGATGTGCATGATTGCCCTAATATATGAAGAGGTATGATACAACCCTAACAGTGATCAAAGAAAATCATAATCTCGTATCGCTCGTAATATAACCTGCCAAGCATACCTCTTGCACAAAGTGATTCTTGTACACAAATAATGTTTTACTCTACAGGAGGTAGCAACGATCCATCCCATCAAAAAATAAGTATTTCATGACTTACTAATGATCTCTTAAAATATTAAGAAAAACTGACGGAACATAAATTCTTTATGCTTCAAGCTGTGGAGGAGGTGTTTGGTATTGGCTATTGTTATATTACAATCAATAACAAGCTTGTAAAAATATTGTTCTTGTTTCAAGAGGTAGATTGTGACCGGAAATGCTAAACTAATGATGAAGATTAATGCGGAGGTCTGATAAGAATAAACCTTATTATTCAGATTAGGCCCCAAGAGGCATTCTTCATCTCCTTTTAGCAAAGTACTATTTCAGGGTAGTCCAATTAGTGGCACGTCTTTTAGCTGTATATCAGTCGCCCCTGAGATACGCCACAAAAGTGTCTCTAAGCTAAATTGGTCTGTACACATCCCATACATTGTATTAGGGGCAATAATATCTAATTGAACTTAGCCGTTTAAAATTTAGTGCATAAATCTGGGCTAACACCACCAGGTCAACTCCATTGGCTGAAAAGAAGCTTACCTACAACGAACATCACTTTGAGCGCCCTCACAATTAAAAAATAGGAACGTCGTTCCAACAATCGAGCGCAAGGTTTCAAGGTTGAACTGAGAGTGTCTAGACAACAAAATATTGATACTCCAGACACCAAGCAAGACCTGAGAAAAAACCATGGCTAAAGCTACGGGACGATACAATCTAATATCGCCCAAAAAGGACCTGGAGAAAGGGGTTGTCTTAAGCGACCTCTGTAACTTCTTAGTTAGCCAAACTATTCAGGGGTGGAAGGTTTATTGGGCTGGTATTGAGTTTGATGTGACTCACAAAGGAATGGCCCTATTGCATAGACTGAAAACTAATGACTTTGCCCCTGCATGGTCAATGACAAGGAATCTCTTTCCTCATTTATTTCAAAATCCGAATTCCACAATTGAATCACCGCTGTGGGCATTGAGAGTCATCCTTGCAGCAGGGATACAGGACCAGCTGATTGACCAGTCTTTGATTGAACCCTTAGCAGGAGCCCTTGGTCTGATCTCTGATTGGCTGCTAACAACCAACACTAACCATTTCAACATGCGAACACAACGTGTCAAGGAACAATTGAGCCTAAAAATGCTGTCGTTGATTCGATCCAATATTCTCAAGTTTATTAACAAATTGGATGCTCTACATGTCGTGAACTACAACGGATTGTTGAGCAGTATTGAAATTGGAACTCAAAATCATACAATCATCATAACTCGAACTAACATGGGTTTTCTGGTGGAGCTCCAAGAACCCGACAAATCGGCAATGAACCGCATGAAGCCTGGGCCGGCGAAATTTTCCCTCCTTCATGAGTCCACACTGAAAGCATTTACACAAGGATCCTCGACACGAATGCAAAGTTTGATTCTTGAATTTAATAGCTCTCTTGCTATCTAACTAAGGTAGAATACTTCATATTGAGCTAACTCATATATGCTGACTCAATAGTTATCTTGACATCTCTGCTTTCATAATCAGATATATAAGCATAATAAATAAATACTCATATTTCTTGATAATTTGTTTAACCACAGATAAATCCTCACTGTAAGCCAGCTTCCAAGTTGACACCCTTACAAAAACCAGGACTCAGAATCCCTCAAACAAGAGATTCCAAGACAACATCATAGAATTGCTTTATTATATGAATAAGCATTTTATCACCAGAAATCCTATATACTAAATGGTTAATTGTAACTGAACCCGCAGGTCACATGTGTTAGGTTTCACAGATTCTATATATTACTAACTCTATACTCGTAATTAACATTAGATAAGTAGATTAAGAAAAAAGCCTGAGGAAGATTAAGAAAAACTGCTTATTGGGTCTTTCCGTGTTTTAGATGAAGCAGTTGAAATTCTTCCTCTTGATATTAAATGGCTACACAACATACCCAATACCCAGACGCTAGGTTATCATCACCAATTGTATTGGACCAATGTGACCTAGTCACTAGAGCTTGCGGGTTATATTCATCATACTCCCTTAATCCGCAACTACGCAACTGTAAACTCCCGAAACATATCTACCGTTTGAAATACGATGTAACTGTTACCAAGTTCTTGAGTGATGTACCAGTGGCGACATTGCCCATAGATTTCATAGTCCCAGTTCTTCTCAAGGCACTGTCAGGCAATGGATTCTGTCCTGTTGAGCCGCGGTGCCAACAGTTCTTAGATGAAATCATTAAGTACACAATGCAAGATGCTCTCTTCTTGAAATATTATCTCAAAAATGTGGGTGCTCAAGAAGACTGTGTTGATGAACACTTTCAAGAGAAAATCTTATCTTCAATTCAGGGCAATGAATTTTTACATCAAATGTTTTTCTGGTATGATCTGGCTATTTTAACTCGAAGGGGTAGATTAAATCGAGGAAACTCTAGATCAACATGGTTTGTTCATGATGATTTAATAGACATCTTAGGCTATGGGGACTATGTTTTTTGGAAGATCCCAATTTCAATGTTACCACTGAACACACAAGGAATCCCCCATGCTGCTATGGACTGGTATCAGGCATCAGTATTCAAAGAAGCGGTTCAAGGGCATACACACATTGTTTCTGTTTCTACTGCCGACGTCTTGATAATGTGCAAAGATTTAATTACATGTCGATTCAACACAACTCTAATCTCAAAAATAGCAGAGATTGAGGATCCAGTTTGTTCTGATTATCCCAATTTTAAGATTGTGTCTATGCTTTACCAGAGCGGAGATTACTTACTCTCCATATTAGGGTCTGATGGGTATAAAATTATTAAGTTCCTCGAACCATTGTGCTTGGCCAAAATTCAATTATGCTCAAAGTACACTGAGAGGAAGGGCCGATTCTTAACACAAATGCATTTAGCTGTAAATCACACCCTAGAAGAAATTACAGAAATGCGTGCACTAAAGCCTTCACAGGCTCAAAAGATCCGTGAATTCCATAGAACATTGATAAGGCTGGAGATGACGCCACAACAACTTTGTGAGCTATTTTCCATTCAAAAACACTGGGGGCATCCTGTGCTACATAGTGAAACAGCAATCCAAAAAGTTAAAAAACATGCTACGGTGCTAAAAGCATTACGCCCTATAGTGATTTTCGAGACATACTGTGTTTTTAAATATAGTATTGCCAAACATTATTTTGATAGTCAAGGATCTTGGTACAGTGTTACTTCAGATAGGAATCTAACACCGGGTCTTAATTCTTATATCAAAAGAAATCAATTCCCTCCGTTGCCAATGATTAAAGAACTACTATGGGAATTTTACCACCTTGACCACCCTCCACTTTTCTCAACCAAAATTATTAGTGACTTAAGTATTTTTATAAAAGACAGAGCTACCGCAGTAGAAAGGACATGCTGGGATGCAGTATTCGAGCCTAATGTTCTAGGATATAATCCACCTCACAAATTTAGTACTAAACGTGTACCGGAACAATTTTTAGAGCAAGAAAACTTTTCTATTGAGAATGTTCTTTCCTACGCACAAAAACTCGAGTATCTACTACCACAATATCGGAACTTTTCTTTCTCATTGAAAGAGAAAGAGTTGAATGTAGGTAGAACCTTCGGAAAATTGCCTTATCCGACTCGCAATGTTCAAACACTTTGTGAAGCTCTGTTAGCTGATGGTCTTGCTAAAGCATTTCCTAGCAATATGATGGTAGTTACGGAACGTGAGCAAAAAGAAAGCTTATTGCATCAAGCATCATGGCACCACACAAGTGATGATTTTGGTGAACATGCCACAGTTAGAGGGAGTAGCTTTGTAACTGATTTAGAGAAATACAATCTTGCATTTAGATATGAGTTTACAGCACCTTTTATAGAATATTGCAACCGTTGCTATGGTGTTAAGAATGTTTTTAATTGGATGCATTATACAATCCCACAGTGTTATATGCATGTCAGTGATTATTATAATCCACCACATAACCTCACACTGGAGAATCGAGACAACCCCCCCGAAGGGCCTAGTTCATACAGGGGTCATATGGGAGGGATTGAAGGACTGCAACAAAAACTCTGGACAAGTATTTCATGTGCTCAAATTTCTTTAGTTGAAATTAAGACTGGTTTTAAGTTACGCTCAGCTGTGATGGGTGACAATCAGTGCATTACTGTTTTATCAGTCTTCCCCTTAGAGACTGACGCAGACGAGCAGGAACAGAGCGCCGAAGACAATGCAGCGAGGGTGGCCGCCAGCCTAGCAAAAGTTACAAGTGCCTGTGGAATCTTTTTAAAACCTGATGAAACATTTGTACATTCAGGTTTTATCTATTTTGGAAAAAAACAATATTTGAATGGGGTCCAATTGCCTCAGTCCCTTAAAACGGCTACAAGAATGGCACCATTGTCTGATGCAATTTTTGATGATCTTCAAGGGACCCTGGCTAGTATAGGCACTGCTTTTGAGCGATCCATCTCTGAGACACGACATATCTTTCCTTGCAGGATAACCGCAGCTTTCCATACGTTTTTTTCGGTGAGAATCTTGCAATATCATCATCTCGGGTTCAATAAAGGTTTTGACCTTGGACAGTTAACACTCGGCAAACCTCTGGATTTCGGAACAATATCATTGGCACTAGCGGTACCGCAGGTGCTTGGAGGGTTATCCTTCTTGAATCCTGAGAAATGTTTCTACCGGAATCTAGGAGATCCAGTTACCTCAGGCTTATTCCAGTTAAAAACTTATCTCCGAATGATTGAGATGGATGATTTATTCTTACCTTTAATTGCGAAGAACCCTGGGAACTGCACTGCCATTGACTTTGTGCTAAATCCTAGCGGATTAAATGTCCCTGGGTCGCAAGACTTAACTTCATTTCTGCGCCAGATTGTACGCAGGACCATCACCCTAAGTGCGAAAAACAAACTTATTAATACCTTATTTCATGCGTCAGCTGACTTCGAAGACGAAATGGTTTGTAAATGGCTATTATCATCAACTCCTGTTATGAGTCGTTTTGCGGCCGATATCTTTTCACGCACGCCGAGCGGGAAGCGATTGCAAATTCTAGGATACCTGGAAGGAACACGCACATTATTAGCCTCTAAGATCATCAACAATAATACAGAGACACCGGTTTTGGACAGACTGAGGAAAATAACATTGCAAAGGTGGAGCCTATGGTTTAGTTATCTTGATCATTGTGATAATATCCTGGCGGAGGCTTTAACCCAAATAACTTGCACAGTTGATTTAGCACAGATTCTGAGGGAATATTCATGGGCTCATATTTTAGAGGGAAGACCTCTTATTGGAGCCACACTCCCATGTATGATTGAGCAATTCAAAGTGTTTTGGCTGAAACCCTACGAACAATGTCCGCAGTGTTCAAATGCAAAGCAACCAGGTGGGAAACCATTCGTGTCAGTGGCAGTCAAGAAACATATTGTTAGTGCATGGCCGAACGCATCCCGAATAAGCTGGACTATCGGGGATGGAATCCCATACATTGGATCAAGGACAGAAGATAAGATAGGACAACCTGCTATTAAACCAAAATGTCCTTCCGCAGCCTTAAGAGAGGCCATTGAATTGGCGTCCCGTTTAACATGGGTAACTCAAGGCAGTTCGAACAGTGACTTGCTAATAAAACCATTTTTGGAAGCACGAGTAAATTTAAGTGTTCAAGAAATACTTCAAATGACCCCTTCACATTACTCAGGAAATATTGTTCACAGGTACAACGATCAATACAGTCCTCATTCTTTCATGGCCAATCGTATGAGTAATTCAGCAACGCGATTGATTGTTTCTACAAACACTTTAGGTGAGTTTTCAGGAGGTGGCCAGTCTGCACGCGACAGCAATATTATTTTCCAGAATGTTATAAATTATGCAGTTGCACTGTTCGATATTAAATTTAGAAACACTGAGGCTACAGATATCCAATATAATCGTGCTCACCTTCATCTAACTAAGTGTTGCACCCGGGAAGTACCAGCTCAGTATTTAACATACACATCTACATTGGATTTAGATTTAACAAGATACCGAGAAAACGAATTGATTTATGACAGTAATCCTCTAAAAGGAGGACTCAATTGCAATATCTCATTCGATAATCCATTTTTCCAAGGTAAACGGCTGAACATTATAGAAGATGATCTTATTCGACTGCCTCACTTATCTGGATGGGAGCTAGCCAAGACCATCATGCAATCAATTATTTCAGATAGCAACAATTCATCTACAGACCCAATTAGCAGTGGAGAAACAAGATCATTCACTACCCATTTCTTAACTTATCCCAAGATAGGACTTCTGTACAGTTTTGGGGCCTTTGTAAGTTATTATCTTGGCAATACAATTCTTCGGACTAAGAAATTAACACTTGACAATTTTTTATATTACTTAACTACTCAAATTCATAATCTACCACATCGCTCATTGCGAATACTTAAGCCAACATTCAAACATGCAAGCGTTATGTCACGGTTAATGAGTATTGATCCTCATTTTTCTATTTACATAGGCGGTGCTGCAGGTGACAGAGGACTCTCAGATGCGGCCAGGTTATTTTTGAGAACGTCCATTTCATCTTTTCTTACATTTGTAAAAGAATGGATAATTAATCGCGGAACAATTGTCCCTTTATGGATAGTATATCCGCTAGAGGGTCAAAACCCAACACCTGTGAATAATTTTCTCTATCAGATCGTAGAACTGCTGGTGCATGATTCATCAAGACAACAGGCTTTTAAAACTACCATAAGTGATCATGTACATCCTCACGACAATCTTGTTTACACATGTAAGAGTACAGCCAGCAATTTCTTCCATGCATCATTGGCGTACTGGAGGAGCAGACACAGAAACAGCAACCGAAAATACTTGGCAAGAGACTCTTCAACTGGATCAAGCACAAACAACAGTGATGGTCATATTGAGAGAAGTCAAGAACAAACCACCAGAGATCCACATGATGGCACTGAACGGAATCTAGTCCTACAAATGAGCCATGAAATAAAAAGAACGACAATTCCACAAGAAAACACGCACCAGGGTCCGTCGTTCCAGTCCTTTCTAAGTGACTCTGCTTGTGGTACAGCAAATCCAAAACTAAATTTCGATCGATCGAGACACAATGTGAAATTTCAGGATCATAACTCGGCATCCAAGAGGGAAGGTCATCAAATAATCTCACACCGTCTAGTCCTACCTTTCTTTACATTATCTCAAGGGACACGCCAATTAACGTCATCCAATGAGTCACAAACCCAAGACGAGATATCAAAGTACTTACGGCAATTGAGATCCGTCATTGATACCACAGTTTATTGTAGATTTACCGGTATAGTCTCGTCCATGCATTACAAACTTGATGAGGTCCTTTGGGAAATAGAGAGTTTCAAGTCGGCTGTGACGCTAGCAGAGGGAGAAGGTGCTGGTGCCTTACTATTGATTCAGAAATACCAAGTTAAGACCTTATTTTTCAACACGCTAGCTACTGAGTCCAGTATAGAGTCAGAAATAGTATCAGGAATGACTACTCCTAGGATGCTTCTACCTGTTATGTCAAAATTCCATAATGACCAAATTGAGATTATTCTTAACAACTCAGCAAGCCAAATAACAGACATAACAAATCCTACTTGGTTTAAAGACCAAAGAGCAAGGCTACCTAAGCAAGTCGAGGTTATAACCATGGATGCAGAGACAACAGAGAATATAAACAGATCGAAATTGTACGAAGCTGTATATAAATTGATCTTACACCATATTGATCCTAGCGTATTGAAAGCAGTGGTCCTTAAAGTCTTTCTAAGTGATACTGAGGGTATGTTATGGCTAAATGATAATTTAGCCCCGTTTTTTGCCACTGGTTATTTAATTAAGCCAATAACGTCAAGTGCTAGATCTAGTGAGTGGTATCTTTGTCTGACGAACTTCTTATCAACTACACGTAAGATGCCACACCAAAACCATCTCAGTTGTAAACAGGTAATACTTACGGCATTGCAACTGCAAATTCAACGAAGCCCATACTGGCTAAGTCATTTAACTCAGTATGCTGACTGTGAGTTACATTTAAGTTATATCCGCCTTGGTTTTCCATCATTAGAGAAAGTACTATACCACAGGTATAACCTCGTCGATTCAAAAAGAGGTCCACTAGTCTCTATCACTCAGCACTTAGCACATCTTAGAGCAGAGATTCGAGAATTAACTAATGATTATAATCAACAGCGACAAAGTCGGACTCAAACATATCACTTTATTCGTACTGCAAAAGGACGAATCACAAAACTAGTCAATGATTATTTAAAATTCTTTCTTATTGTGCAAGCATTAAAACATAATGGGACATGGCAAGCTGAGTTTAAGAAATTACCAGAGTTGATTAGTGTGTGCAATAGGTTCTACCATATTAGAGATTGCAATTGTGAAGAACGTTTCTTAGTTCAAACCTTATATTTACATAGAATGCAGGATTCTGAAGTTAAGCTTATCGAAAGGCTGACAGGGCTTCTGAGTTTATTTCCGGATGGTCTCTACAGGTTTGATTGAATTACCGTGCATAGTATCCTGATACTTGCAAAGGTTGGTTATTAACATACAGATTATAAAAAACTCATAAATTGCTCTCATACATCATATTGATCTAATCTCAATAAACAACTATTTAAATAACGAAAGGAGTCCCTATATTATATACTATATTTAGCCTCTCTCCCTGCGTGATAATCAAAAAATTCACAATGCAGCATGTGTGACATATTACTGCCGCAATGAATTTAACGCAACATAATAAACTCTGCACTCTTTATAATTAAGCTTTAACGAAAGGTCTGGGCTCATATTGTTATTGATATAATAATGTTGTATCAATATCCTGTCAGATGGAATAGTGTTTTGGTTGATAACACAACTTCTTAAAACAAAATTGATCTTTAAGATTAAGTTTTTTATAATTATCATTACTTTAATTTGTCGTTTTAAAAACGGTGATAGCCTTAATCTTTGTGTAAAATAAGAGATTAGGTGTAATAACCTTAACATTTTTGTCTAGTAAGCTACTATTTCATACAGAATGATAAAATTAAAAGAAAAGGCAGGACTGTAAAATCAGAAATACCTTCTTTACAATATAGCAGACTAGATAATAATCTTCGTGTTAATGATAATTAAGACATTGACCACGCTCATCAGAAGGCTCGCCAGAATAAACGTTGCAAAAAGGATTCCTGGAAAAATGGTCGCACACAAAAATTTAAAAATAAATCTATTTCTTCTTTTTTGTGTGTCCA diff --git a/preprocessing/nextclade/tests/ebola-dataset/minimizer/minimizer.json b/preprocessing/nextclade/tests/ebola-multipath-dataset/minimizer/minimizer.json similarity index 100% rename from preprocessing/nextclade/tests/ebola-dataset/minimizer/minimizer.json rename to preprocessing/nextclade/tests/ebola-multipath-dataset/minimizer/minimizer.json diff --git a/preprocessing/nextclade/tests/test_nextclade_preprocessing.py b/preprocessing/nextclade/tests/test_nextclade_preprocessing.py index 6f31580cde..32e3de184d 100644 --- a/preprocessing/nextclade/tests/test_nextclade_preprocessing.py +++ b/preprocessing/nextclade/tests/test_nextclade_preprocessing.py @@ -1,8 +1,6 @@ # ruff: noqa: S101 -import os -import shutil from pathlib import Path from typing import Literal @@ -46,7 +44,7 @@ EBOLA_SUDAN_DATASET = "tests/ebola-dataset/ebola-sudan" EBOLA_ZAIRE_DATASET = "tests/ebola-dataset/ebola-zaire" -MULTI_EBOLA_DATASET = "tests/ebola-dataset" +MULTI_EBOLA_DATASET = "tests/ebola-multipath-dataset" SINGLE_SEGMENT_EMBL = "tests/flatfiles/single_segment.embl" @@ -58,7 +56,7 @@ def consensus_sequence( next( SeqIO.parse( (EBOLA_ZAIRE_DATASET if type == "ebola-zaire" else EBOLA_SUDAN_DATASET) - + "/reference.fasta", + + "/main/reference.fasta", "fasta", ) ).seq @@ -928,19 +926,6 @@ def invalid_sequence() -> str: ] -def copy_dataset(source_folder: str, destination_folder: str) -> None: - os.makedirs(destination_folder, exist_ok=True) - - for item in os.listdir(source_folder): - src_path = os.path.join(source_folder, item) - - if not os.path.isfile(src_path): - continue - - dst_path = os.path.join(destination_folder, item) - shutil.copy2(src_path, dst_path) - - def process_single_entry( test_case: ProcessingTestCase, config: Config, dataset_dir: str = "temp" ) -> SubmissionData: @@ -957,8 +942,6 @@ def test_preprocessing_single_segment(test_case_def: Case): config = get_config(SINGLE_SEGMENT_CONFIG, ignore_args=True) factory_custom = ProcessedEntryFactory(all_metadata_fields=list(config.processing_spec.keys())) test_case = test_case_def.create_test_case(factory_custom) - # process_single expects dataset to be in a subfolder with the segment name (in this case "main") - copy_dataset(EBOLA_SUDAN_DATASET, os.path.join(EBOLA_SUDAN_DATASET, "main")) processed_entry = process_single_entry(test_case, config, EBOLA_SUDAN_DATASET) verify_processed_entry( processed_entry.processed_entry, test_case.expected_output, test_case.name From cf9d51fe06b54b47b7433a007b05bdf69d8a3338 Mon Sep 17 00:00:00 2001 From: "Anna (Anya) Parker" <50943381+anna-parker@users.noreply.github.com> Date: Thu, 27 Nov 2025 22:01:40 +0100 Subject: [PATCH 26/44] Integration test fix (#5560) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolves # ### Screenshot ### PR Checklist - [ ] All necessary documentation has been adapted. - [ ] The implemented feature is covered by appropriate, automated tests. - [ ] Any manual testing that has been done is documented (i.e. what exactly was tested?) 🚀 Preview: Add `preview` label to enable --- .../docs/for-administrators/my-first-loculus.md | 3 ++- .../setup-with-k3d-and-nginx.mdx | 3 ++- .../tests/test-data/cchfv_test_metadata.tsv.zst | Bin 881 -> 899 bytes kubernetes/loculus/values.yaml | 2 +- preprocessing/nextclade/README.md | 10 ++++++---- 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/docs/src/content/docs/for-administrators/my-first-loculus.md b/docs/src/content/docs/for-administrators/my-first-loculus.md index 80dfaa7a72..0a3fb5ffad 100644 --- a/docs/src/content/docs/for-administrators/my-first-loculus.md +++ b/docs/src/content/docs/for-administrators/my-first-loculus.md @@ -152,8 +152,9 @@ organisms: - 'prepro' configFile: log_level: DEBUG - genes: [] batch_size: 100 + nucleotideSequences: + - name: 'main' referenceGenomes: singleReference: nucleotideSequences: diff --git a/docs/src/content/docs/for-administrators/setup-with-k3d-and-nginx.mdx b/docs/src/content/docs/for-administrators/setup-with-k3d-and-nginx.mdx index 5c66cf27b8..d083bfdc97 100644 --- a/docs/src/content/docs/for-administrators/setup-with-k3d-and-nginx.mdx +++ b/docs/src/content/docs/for-administrators/setup-with-k3d-and-nginx.mdx @@ -169,8 +169,9 @@ organisms: - 'prepro' configFile: log_level: DEBUG - genes: [] batch_size: 100 + nucleotideSequences: + - name: 'main' referenceGenomes: singleReference: nucleotideSequences: diff --git a/integration-tests/tests/test-data/cchfv_test_metadata.tsv.zst b/integration-tests/tests/test-data/cchfv_test_metadata.tsv.zst index 34d4a0db3a6cbcf4f46b115042e5e2bf094f8e29..44add209705396fc34851824dca3bc754da8eb14 100644 GIT binary patch literal 899 zcmV-}1AP1_wJ-f-F9xj}0Jd_IGEi&O0WdR@h^ngAyx3$)Vml=%Mk0GBk(q+|@p~m= zL7E>*QiX)_RFV;PGI278SDqex0Dk~|0PbR~gvzF5(&{^Bk|{>Xe&60w%j%sL>2M+& zCw#ik{}oKUCj%X{eo#m4-_lKQ5}QpisKZEU43IUwU1Zm$u2FVibJGD2Z`zA|-Uk zN%>n{3oRIBnF3ar+(+eePPUz`hhaa-xtbU!mnz&8G6~%Vxx< z)L8KvTD-1>_+!;%Pa6I8NbA{?>Hq>T9u5No93YK{qrl^V zfWrfYg`t!H0SYFO2!!E41H%AF14KDAI6Nq&d{ZZMhaFGU;=4w`i&FlL6C|D}hU0w ziN3@yG7XYq*Mbd`5fEU|VE%=-5pY5YzNe!iAYQaBRU9@4f$_Oy=z@by54Id57(>OO zyE-+{CiI@%zHUW9`y;T{(5j0NNvK#4t8MM9!)>jaff!84N!vqo^D2S(cgnS80CEL| zNr0Dk~{0NosuWvG=|0@mFiN$P7F&dmCHw%t#Pkf#QkI< z);RZNebl8Ei_?7hhWCxnLhXE{Sg4foo7Mn~}PEbx1^70j0ZpCb#XFr=0 zpHkyRTZr+x4=7m1Z?X)DxlHrBPF7;D|00SqjppaCG6MI+s?qG{jDKPoC|bu?QoqHD zpZjV3z7%OSgYJ&BT45=&1g{W5lpZBUQ>BF@Z&^9XcyprDlI@;~`@u(v4eV_06xO-8 ze_4`wUDzg^v2SQ*P zq`&mG0*)x*_f%9Q!V9>i;9>g^q0e=OXgD-_;N@z;U{sts(5d2W;`cP|qgEKSA)>Wj zt-3BEp~aH0I(BDk-Imo%%wQoqZ9Sr!t^$bLro322B^N4$3jvJdV5f|XhxgG$0>mXq z^b61JB81mq?s0$!ax|=*C7b&ZdoU^hAYrFyi)o~lc{eUs!-V}(ln1?^N$XjJc_OkS zViPk!tn%gcb=LKCt%v@tJ7+~V`PS$6KLx&!LWRqPfL*Hl92AP-QX1>~bq&m>O_rHd Hm*~Feh=iXK diff --git a/kubernetes/loculus/values.yaml b/kubernetes/loculus/values.yaml index 6f0b04f099..91d6c3b1b6 100644 --- a/kubernetes/loculus/values.yaml +++ b/kubernetes/loculus/values.yaml @@ -1420,6 +1420,7 @@ defaultOrganisms: - <<: *preprocessing version: 1 configFile: + <<: *preprocessingConfigFile nucleotideSequences: - nextclade_dataset_name: nextstrain/wnv/all-lineages nextclade_dataset_server: https://raw.githubusercontent.com/nextstrain/nextclade_data/wnv/data_output @@ -1677,7 +1678,6 @@ defaultOrganisms: - "prepro" configFile: log_level: DEBUG - genes: [] batch_size: 100 referenceGenomes: singleReference: diff --git a/preprocessing/nextclade/README.md b/preprocessing/nextclade/README.md index d6b6155bda..1cdb61fb0f 100644 --- a/preprocessing/nextclade/README.md +++ b/preprocessing/nextclade/README.md @@ -224,8 +224,9 @@ To add multiple preprocessing pipelines alter the preprocessing section of the ` version: 1 dockerTag: commit-xxxxx configFile: - nextclade_dataset_name: nextstrain/wnv/all-lineages - genes: [capsid, prM, env, NS1, NS2A, NS2B, NS3, NS4A, 2K, NS4B, NS5] + nucleotideSequences: + - name: main # default value, not actually required + nextclade_dataset_name: nextstrain/wnv/all-lineages batch_size: 100 - image: ghcr.io/loculus-project/preprocessing-nextclade args: @@ -233,7 +234,8 @@ To add multiple preprocessing pipelines alter the preprocessing section of the ` version: 2 dockerTag: commit-yyyyyyy configFile: - nextclade_dataset_name: nextstrain/wnv/all-lineages - genes: [capsid, prM, env, NS1, NS2A, NS2B, NS3, NS4A, 2K, NS4B, NS5] + nucleotideSequences: + - name: main + nextclade_dataset_name: nextstrain/wnv/all-lineages batch_size: 100 ``` From 9cd1d30f9f53d9d5d71b74913748162d8ce973ef Mon Sep 17 00:00:00 2001 From: "Anna (Anya) Parker" <50943381+anna-parker@users.noreply.github.com> Date: Fri, 28 Nov 2025 17:49:19 +0100 Subject: [PATCH 27/44] feat(prepro, config): use new EV datasets and fix gene alignment for multi-path by prepro (#5563) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolves https://github.com/loculus-project/loculus/issues/5574 Adds back the requirement to list genes in the prepro config file. However, we now must list them with a nextclade dataset. (This is all on a branch and the values.schema still needs to be updated) ### Screenshot image ### PR Checklist - [ ] All necessary documentation has been adapted. - [ ] The implemented feature is covered by appropriate, automated tests. - [ ] Any manual testing that has been done is documented (i.e. what exactly was tested?) 🚀 Preview: https://update-evs.loculus.org --- .../for-administrators/my-first-loculus.md | 1 + .../setup-with-k3d-and-nginx.mdx | 1 + .../setup-with-kubernetes.md | 6 ++-- .../loculus-preprocessing-config.yaml | 6 ---- kubernetes/loculus/values.yaml | 34 ++++++++++++++----- .../src/loculus_preprocessing/config.py | 8 +++-- .../src/loculus_preprocessing/nextclade.py | 23 ++++++------- .../tests/multi_pathogen_config.yaml | 7 ++-- .../nextclade/tests/multi_segment_config.yaml | 7 ++-- .../tests/single_segment_config.yaml | 4 +-- 10 files changed, 52 insertions(+), 45 deletions(-) diff --git a/docs/src/content/docs/for-administrators/my-first-loculus.md b/docs/src/content/docs/for-administrators/my-first-loculus.md index 0a3fb5ffad..30524dfd7a 100644 --- a/docs/src/content/docs/for-administrators/my-first-loculus.md +++ b/docs/src/content/docs/for-administrators/my-first-loculus.md @@ -155,6 +155,7 @@ organisms: batch_size: 100 nucleotideSequences: - name: 'main' + genes: [] referenceGenomes: singleReference: nucleotideSequences: diff --git a/docs/src/content/docs/for-administrators/setup-with-k3d-and-nginx.mdx b/docs/src/content/docs/for-administrators/setup-with-k3d-and-nginx.mdx index d083bfdc97..4f6f383c18 100644 --- a/docs/src/content/docs/for-administrators/setup-with-k3d-and-nginx.mdx +++ b/docs/src/content/docs/for-administrators/setup-with-k3d-and-nginx.mdx @@ -172,6 +172,7 @@ organisms: batch_size: 100 nucleotideSequences: - name: 'main' + genes: [] referenceGenomes: singleReference: nucleotideSequences: diff --git a/docs/src/content/docs/for-administrators/setup-with-kubernetes.md b/docs/src/content/docs/for-administrators/setup-with-kubernetes.md index 14aaad5a8c..d928559589 100644 --- a/docs/src/content/docs/for-administrators/setup-with-kubernetes.md +++ b/docs/src/content/docs/for-administrators/setup-with-kubernetes.md @@ -144,8 +144,10 @@ preprocessing: - 'prepro' configFile: log_level: DEBUG - nextclade_dataset_name: nextstrain/ebola/zaire - genes: [NP, VP35, VP40, GP, sGP, ssGP, VP30, VP24, L] + nucleotideSequences: + - name: 'main' + nextclade_dataset_name: nextstrain/ebola/zaire + genes: [NP, VP35, VP40, GP, sGP, ssGP, VP30, VP24, L] batch_size: 100 ``` diff --git a/kubernetes/loculus/templates/loculus-preprocessing-config.yaml b/kubernetes/loculus/templates/loculus-preprocessing-config.yaml index 1602ff0047..90b6978e5c 100644 --- a/kubernetes/loculus/templates/loculus-preprocessing-config.yaml +++ b/kubernetes/loculus/templates/loculus-preprocessing-config.yaml @@ -1,11 +1,6 @@ {{- range $organism, $organismConfig := (include "loculus.enabledOrganisms" . | fromJson) }} {{- $metadata := ($organismConfig.schema | include "loculus.patchMetadataSchema" | fromYaml).metadata }} {{- $referenceGenomes:= include "loculus.mergeReferenceGenomes" $organismConfig.referenceGenomes | fromYaml }} -{{- $genesList := (eq $referenceGenomes.genes nil | ternary (list) $referenceGenomes.genes) }} -{{- $genesDict := dict "genes" (list) -}} -{{- range $g := $genesList }} - {{- $_ := set $genesDict "genes" (append ($genesDict.genes) $g.name) -}} -{{- end }} {{- $flattened := include "loculus.flattenPreprocessingVersions" $organismConfig.preprocessing | fromJson }} {{- range $processingIndex, $processingConfig := $flattened.items }} {{- if $processingConfig.configFile }} @@ -21,7 +16,6 @@ data: preprocessing-config.yaml: | organism: {{ $organism }} {{- $preproAndEnaConfigFile | toYaml | nindent 4 }} - {{- $genesDict | toYaml | nindent 4 }} processing_spec: {{- $args := dict "metadata" $metadata "referenceGenomes" $organismConfig.referenceGenomes }} {{- include "loculus.preprocessingSpecs" $args | nindent 6 }} diff --git a/kubernetes/loculus/values.yaml b/kubernetes/loculus/values.yaml index 91d6c3b1b6..56cf23e28f 100644 --- a/kubernetes/loculus/values.yaml +++ b/kubernetes/loculus/values.yaml @@ -1326,6 +1326,9 @@ defaultOrganismConfig: &defaultOrganismConfig log_level: DEBUG batch_size: 100 create_embl_file: true + nucleotideSequences: + - name: "main" + genes: [] nextclade_dataset_server: https://data.clades.nextstrain.org/v3 ingest: &ingest image: ghcr.io/loculus-project/ingest @@ -1347,6 +1350,7 @@ defaultOrganisms: <<: *preprocessingConfigFile nucleotideSequences: - nextclade_dataset_name: nextstrain/ebola/sudan + genes: [NP, VP35, VP40, GP, sGP, ssGP, VP30, VP24, L] nextclade_dataset_server: https://raw.githubusercontent.com/nextstrain/nextclade_data/ebola/data_output ingest: <<: *ingest @@ -1423,6 +1427,7 @@ defaultOrganisms: <<: *preprocessingConfigFile nucleotideSequences: - nextclade_dataset_name: nextstrain/wnv/all-lineages + genes: [capsid, prM, env, NS1, NS2A, NS2B, NS3, NS4A, 2K, NS4B, NS5] nextclade_dataset_server: https://raw.githubusercontent.com/nextstrain/nextclade_data/wnv/data_output ingest: <<: *ingest @@ -1744,10 +1749,13 @@ defaultOrganisms: nucleotideSequences: - name: L nextclade_dataset_name: community/pathoplexus/cchfv/L + genes: [RdRp] - name: M nextclade_dataset_name: community/pathoplexus/cchfv/M + genes: [GPC] - name: S nextclade_dataset_name: community/pathoplexus/cchfv/S + genes: [NP] ingest: <<: *ingest configFile: @@ -1789,13 +1797,13 @@ defaultOrganisms: image: "/images/organisms/enterovirus.jpg" linkOuts: - name: "Nextclade (CV-A16)" - url: "https://clades.nextstrain.org/?input-fasta={{[unalignedNucleotideSequences:CV-A16+rich|fasta]}}&dataset-name=community/hodcroftlab/enterovirus/enterovirus/linked/CV-A16&dataset-server=https://raw.githubusercontent.com/alejandra-gonzalezsanchez/nextclade_data/multi-pathogen-evs/data_output" + url: "https://clades.nextstrain.org/?input-fasta={{[unalignedNucleotideSequences:CV-A16+rich|fasta]}}&dataset-name=enpen/enterovirus/cv-a16&dataset-server=https://raw.githubusercontent.com/nextstrain/nextclade_data/ebola/data_output" - name: "Nextclade (CV-A10)" - url: "https://clades.nextstrain.org/?input-fasta={{[unalignedNucleotideSequences:CV-A10+rich|fasta]}}&dataset-name=community/hodcroftlab/enterovirus/enterovirus/linked/CV-A10&dataset-server=https://raw.githubusercontent.com/alejandra-gonzalezsanchez/nextclade_data/multi-pathogen-evs/data_output" + url: "https://clades.nextstrain.org/?input-fasta={{[unalignedNucleotideSequences:CV-A10+rich|fasta]}}&dataset-name=enpen/enterovirus/cv-a10&dataset-server=https://raw.githubusercontent.com/nextstrain/nextclade_data/ebola/data_output" - name: "Nextclade (EV-A71)" - url: "https://clades.nextstrain.org/?input-fasta={{[unalignedNucleotideSequences:EV-A71+rich|fasta]}}&dataset-name=community/hodcroftlab/enterovirus/enterovirus/linked/EV-A71&dataset-server=https://raw.githubusercontent.com/alejandra-gonzalezsanchez/nextclade_data/multi-pathogen-evs/data_output" + url: "https://clades.nextstrain.org/?input-fasta={{[unalignedNucleotideSequences:EV-A71+rich|fasta]}}&dataset-name=enpen/enterovirus/ev-a71&dataset-server=https://raw.githubusercontent.com/nextstrain/nextclade_data/ebola/data_output" - name: "Nextclade (EV-D68)" - url: "https://clades.nextstrain.org/?input-fasta={{[unalignedNucleotideSequences:EV-D68+rich|fasta]}}&dataset-name=community/hodcroftlab/enterovirus/enterovirus/linked/EV-D68&dataset-server=https://raw.githubusercontent.com/alejandra-gonzalezsanchez/nextclade_data/multi-pathogen-evs/data_output" + url: "https://clades.nextstrain.org/?input-fasta={{[unalignedNucleotideSequences:EV-D68+rich|fasta]}}&dataset-name=enpen/enterovirus/ev-d68" metadataAdd: - &evMetadataAdd name: clade_cv_a16 @@ -1866,19 +1874,27 @@ defaultOrganisms: minimizer_index: "https://raw.githubusercontent.com/alejandra-gonzalezsanchez/loculus-evs/master/evs_minimizer-index.json" nucleotideSequences: - name: CV-A16 - nextclade_dataset_name: community/hodcroftlab/enterovirus/enterovirus/linked/CV-A16 + nextclade_dataset_name: enpen/enterovirus/cv-a16 accepted_sort_matches: ["community/hodcroftlab/enterovirus/cva16", "community/hodcroftlab/enterovirus/enterovirus/linked/CV-A16"] gene_prefix: "CV-A16-" + genes: ["VP4", "VP2", "VP3", "VP1", "2A", "2B", "2C", "3A", "3B", "3C", "3D"] - name: CV-A10 - nextclade_dataset_name: community/hodcroftlab/enterovirus/enterovirus/linked/CV-A10 + nextclade_dataset_name: enpen/enterovirus/cv-a10 + accepted_sort_matches: ["community/hodcroftlab/enterovirus/enterovirus/linked/CV-A10"] gene_prefix: "CV-A10-" + genes: ["VP4", "VP2", "VP3", "VP1", "2A", "2B", "2C", "3A", "3B", "3C", "3D"] - name: EV-A71 - nextclade_dataset_name: community/hodcroftlab/enterovirus/enterovirus/linked/EV-A71 + nextclade_dataset_name: enpen/enterovirus/ev-a71 + accepted_sort_matches: ["community/hodcroftlab/enterovirus/enterovirus/linked/EV-A71"] gene_prefix: "EV-A71-" + genes: ["VP4", "VP2", "VP3", "VP1", "2A", "2B", "2C", "3A", "3B", "3C", "3D"] - name: EV-D68 gene_prefix: "EV-D68-" - nextclade_dataset_name: community/hodcroftlab/enterovirus/enterovirus/linked/EV-D68 - nextclade_dataset_server: https://raw.githubusercontent.com/alejandra-gonzalezsanchez/nextclade_data/multi-pathogen-evs/data_output + accepted_sort_matches: ["community/hodcroftlab/enterovirus/enterovirus/linked/EV-D68"] + genes: ["VP4", "VP2", "VP3", "VP1", "2A", "2B", "2C", "3A", "3B", "3C", "3D"] + nextclade_dataset_name: enpen/enterovirus/ev-d68 + nextclade_dataset_server: https://data.clades.nextstrain.org/v3 + nextclade_dataset_server: https://raw.githubusercontent.com/nextstrain/nextclade_data/evs-datasets/data_output ingest: <<: *ingest configFile: diff --git a/preprocessing/nextclade/src/loculus_preprocessing/config.py b/preprocessing/nextclade/src/loculus_preprocessing/config.py index 02c160784a..5f02009285 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/config.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/config.py @@ -47,6 +47,7 @@ class NextcladeSequenceAndDataset: nextclade_dataset_server: str | None = None accepted_sort_matches: list[str] = dataclasses.field(default_factory=list) gene_prefix: str | None = None + genes: list[str] = dataclasses.field(default_factory=list) @dataclass @@ -65,7 +66,6 @@ class Config: keycloak_token_path: str = "realms/loculus/protocol/openid-connect/token" # noqa: S105 organism: str = "mpox" - genes: list[str] = dataclasses.field(default_factory=list) nucleotideSequences: list[NextcladeSequenceAndDataset] = dataclasses.field( # noqa: N815 default_factory=list ) @@ -91,7 +91,7 @@ class Config: def assign_nextclade_sequence_and_dataset( - nuc_seq_values: list[dict[str, Any]], + nuc_seq_values: list[dict[str, Any]], config: Config ) -> list[NextcladeSequenceAndDataset]: if not isinstance(nuc_seq_values, list): error_msg = f"nucleotideSequences should be a list of dicts, got: {type(nuc_seq_values)}" @@ -105,6 +105,8 @@ def assign_nextclade_sequence_and_dataset( for seq_key, seq_value in value.items(): if hasattr(seq_and_dataset, seq_key) and seq_value is not None: setattr(seq_and_dataset, seq_key, seq_value) + if not seq_and_dataset.nextclade_dataset_server: + seq_and_dataset.nextclade_dataset_server = config.nextclade_dataset_server nextclade_sequence_and_dataset_list.append(seq_and_dataset) return nextclade_sequence_and_dataset_list @@ -127,7 +129,7 @@ def load_config_from_yaml(config_file: str, config: Config | None = None) -> Con for key, value in yaml_config.items(): if value is not None and hasattr(config, key): if key == "nucleotideSequences": - setattr(config, key, assign_nextclade_sequence_and_dataset(value)) + setattr(config, key, assign_nextclade_sequence_and_dataset(value, config)) continue attr = getattr(config, key) if isinstance(attr, StrEnum): diff --git a/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py b/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py index f0e36a1661..e4a001f6d8 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py @@ -85,6 +85,10 @@ def mask_terminal_gaps( ) +def create_gene_name(gene: str, gene_prefix: str | None) -> str: + return gene_prefix + gene if gene_prefix else gene + + def parse_nextclade_tsv( amino_acid_insertions: defaultdict[ AccessionVersion, defaultdict[GeneName, list[AminoAcidInsertion]] @@ -93,7 +97,6 @@ def parse_nextclade_tsv( AccessionVersion, defaultdict[SegmentName, list[NucleotideInsertion]] ], result_dir: str, - config: Config, sequence_and_dataset: NextcladeSequenceAndDataset, ) -> tuple[ defaultdict[AccessionVersion, defaultdict[GeneName, list[AminoAcidInsertion]]], @@ -113,12 +116,8 @@ def parse_nextclade_tsv( if not ins: continue gene, val = ins.split(":", maxsplit=1) - if gene in config.genes: - gene_name = ( - sequence_and_dataset.gene_prefix + gene - if sequence_and_dataset.gene_prefix - else gene - ) + if gene in sequence_and_dataset.genes: + gene_name = create_gene_name(gene, sequence_and_dataset.gene_prefix) amino_acid_insertions[id][gene_name].append(val) else: logger.debug( @@ -675,14 +674,14 @@ def load_aligned_nuc_sequences( def load_aligned_aa_sequences( result_dir_seg: str, - config: Config, + sequence_and_dataset: NextcladeSequenceAndDataset, aligned_aminoacid_sequences: dict[AccessionVersion, dict[GeneName, AminoAcidSequence | None]], ) -> dict[AccessionVersion, dict[GeneName, AminoAcidSequence | None]]: """ Load the nextclade amino acid alignment results into the aligned_aminoacid_sequences dict, mapping each accession to a geneName: AminoAcidSequence dictionary. """ - for gene in config.genes: + for gene in sequence_and_dataset.genes: translation_path = result_dir_seg + f"/nextclade.cds_translation.{gene}.fasta" try: with open(translation_path, encoding="utf-8") as aligned_translations: @@ -690,7 +689,8 @@ def load_aligned_aa_sequences( for aligned_sequence in aligned_translation: sequence_id = aligned_sequence.id masked_sequence = mask_terminal_gaps(str(aligned_sequence.seq), mask_char="X") - aligned_aminoacid_sequences[sequence_id][gene] = masked_sequence + gene_name = create_gene_name(gene, sequence_and_dataset.gene_prefix) + aligned_aminoacid_sequences[sequence_id][gene_name] = masked_sequence except FileNotFoundError: # This can happen if the sequence does not cover this gene logger.debug( @@ -816,7 +816,7 @@ def enrich_with_nextclade( # noqa: PLR0914 result_dir_seg, segment, aligned_nucleotide_sequences ) aligned_aminoacid_sequences = load_aligned_aa_sequences( - result_dir_seg, config, aligned_aminoacid_sequences + result_dir_seg, sequence_and_dataset, aligned_aminoacid_sequences ) nextclade_metadata = parse_nextclade_json( result_dir_seg, nextclade_metadata, segment, unaligned_nucleotide_sequences @@ -825,7 +825,6 @@ def enrich_with_nextclade( # noqa: PLR0914 amino_acid_insertions, nucleotide_insertions, result_dir_seg, - config, sequence_and_dataset, ) diff --git a/preprocessing/nextclade/tests/multi_pathogen_config.yaml b/preprocessing/nextclade/tests/multi_pathogen_config.yaml index 6713d15e04..af4a99aa43 100644 --- a/preprocessing/nextclade/tests/multi_pathogen_config.yaml +++ b/preprocessing/nextclade/tests/multi_pathogen_config.yaml @@ -1,11 +1,6 @@ # Expects that only a single segment is present per sequence entry. batch_size: 100 alignment_requirement: ALL -genes: -- NPEbolaSudan -- VP35EbolaSudan -- VP24EbolaZaire -- LEbolaZaire log_level: DEBUG minimizer_index: TEST nextclade_dataset_server: TEST @@ -13,9 +8,11 @@ nucleotideSequences: - name: ebola-sudan nextclade_dataset_name: ebola-dataset/ebola-sudan accepted_sort_matches: ebola-sudan + genes: [NPEbolaSudan, VP35EbolaSudan, LEbolaSudan] - name: ebola-zaire nextclade_dataset_name: ebola-dataset/ebola-zaire accepted_sort_matches: ebola-zaire + genes: [VP24EbolaZaire, LEbolaZaire] organism: multi-ebola-test processing_spec: subtype: diff --git a/preprocessing/nextclade/tests/multi_segment_config.yaml b/preprocessing/nextclade/tests/multi_segment_config.yaml index a609b357f4..ea1437ca12 100644 --- a/preprocessing/nextclade/tests/multi_segment_config.yaml +++ b/preprocessing/nextclade/tests/multi_segment_config.yaml @@ -1,10 +1,5 @@ batch_size: 100 alignment_requirement: ALL -genes: -- NPEbolaSudan -- VP35EbolaSudan -- VP24EbolaZaire -- LEbolaZaire log_level: DEBUG minimizer_index: TEST segment_classification_method: "minimizer" @@ -12,9 +7,11 @@ nucleotideSequences: - name: ebola-sudan nextclade_dataset_name: ebola-dataset/ebola-sudan accepted_sort_matches: [accepted-name] + genes: [NPEbolaSudan, VP35EbolaSudan, LEbolaSudan] - name: ebola-zaire nextclade_dataset_name: ebola-dataset/ebola-zaire accepted_sort_matches: [accepted-name] + genes: [VP24EbolaZaire, LEbolaZaire] organism: multi-ebola-test processing_spec: totalInsertedNucs_ebola-zaire: diff --git a/preprocessing/nextclade/tests/single_segment_config.yaml b/preprocessing/nextclade/tests/single_segment_config.yaml index e847abda73..41ec8c4380 100644 --- a/preprocessing/nextclade/tests/single_segment_config.yaml +++ b/preprocessing/nextclade/tests/single_segment_config.yaml @@ -1,7 +1,4 @@ batch_size: 100 -genes: -- NPEbolaSudan -- VP35EbolaSudan log_level: DEBUG nextclade_dataset_server: TEST scientific_name: "Test Ebola Sudan Virus" @@ -11,6 +8,7 @@ db_name: "Loculus" minimizer_index: TEST nucleotideSequences: - nextclade_dataset_name: ebola-sudan-test-dataset + genes: [NPEbolaSudan, VP35EbolaSudan, LEbolaSudan] organism: ebola-sudan-test processing_spec: completeness: From 0f579070af9a3eaa8e4dbbb5de32aefc4a2e291b Mon Sep 17 00:00:00 2001 From: anna-parker <50943381+anna-parker@users.noreply.github.com> Date: Sat, 29 Nov 2025 09:57:47 +0100 Subject: [PATCH 28/44] merge conflict --- .../src/loculus_preprocessing/prepro.py | 56 ------------------- 1 file changed, 56 deletions(-) diff --git a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py index 113f947692..5563135bae 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py @@ -288,62 +288,6 @@ def get_sequence_length( return len(sequence) if sequence else 0 -def add_alignment_errors_warnings( - unprocessed: UnprocessedAfterNextclade, - config: Config, - errors: list[ProcessingAnnotation], - warnings: list[ProcessingAnnotation], -) -> tuple[list[ProcessingAnnotation], list[ProcessingAnnotation]]: - if not unprocessed.nextcladeMetadata and unprocessed.unalignedNucleotideSequences: - message = ( - "An unknown internal error occurred while aligning sequences, " - "please contact the administrator." - ) - errors.append( - ProcessingAnnotation.from_single( - "alignment", AnnotationSourceType.NUCLEOTIDE_SEQUENCE, message=message - ) - ) - return (errors, warnings) - aligned_segments = set() - for segment in config.nucleotideSequences: - segment_name = segment.name - if segment_name not in unprocessed.unalignedNucleotideSequences: - continue - if unprocessed.nextcladeMetadata and ( - segment_name not in unprocessed.nextcladeMetadata - or (unprocessed.nextcladeMetadata[segment_name] is None) - ): - message = ( - "Nucleotide sequence failed to align" - if not config.multi_segment - else f"Nucleotide sequence for {segment_name} failed to align" - ) - annotation = ProcessingAnnotation.from_single( - segment_name, AnnotationSourceType.NUCLEOTIDE_SEQUENCE, message=message - ) - if config.multi_segment and config.alignment_requirement == AlignmentRequirement.ANY: - warnings.append(annotation) - else: - errors.append(annotation) - continue - aligned_segments.add(segment_name) - - if ( - not aligned_segments - and config.multi_segment - and len(unprocessed.unalignedNucleotideSequences) > 0 - ): - errors.append( - ProcessingAnnotation.from_single( - ProcessingAnnotationAlignment, - AnnotationSourceType.NUCLEOTIDE_SEQUENCE, - message="No segment aligned.", - ) - ) - return (errors, warnings) - - def get_output_metadata( accession_version: AccessionVersion, unprocessed: UnprocessedData | UnprocessedAfterNextclade, From a6edb8edb0f9de46e04ce267199d5382d04bf875 Mon Sep 17 00:00:00 2001 From: "Anna (Anya) Parker" <50943381+anna-parker@users.noreply.github.com> Date: Mon, 1 Dec 2025 13:33:58 +0100 Subject: [PATCH 29/44] feat(docs, website): multi path - update submission docs and templates with correct fields (#5561) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolves https://github.com/loculus-project/loculus/issues/5572 Follow up PR with `fastaId` to `fastaIds` change: https://github.com/loculus-project/loculus/pull/5583 ### Screenshot fastaIds field added to template for CCHF: image image Not added for ebola or EVs: image image ### PR Checklist - [ ] Make PR with same changes in PPX -> after docs are approved - ~[x] Add fastaIds to commonMetadata fields in config?~ -> fastaIds field does not exist for single segmented organisms and thus should not be added here as this breaks Loculus - [x] Ensure metadata template downloads are correct 🚀 Preview: https://multipath-docs.loculus.org --------- Co-authored-by: Theo Sanderson --- .../SubmissionControllerDescriptions.kt | 6 +- .../org/loculus/backend/model/SubmitModel.kt | 4 +- .../loculus/backend/utils/MetadataEntry.kt | 16 ++-- .../submission/SubmitEndpointTest.kt | 6 +- .../test/resources/metadata_multi_segment.tsv | 2 +- .../revised_metadata_multi_segment.tsv | 2 +- .../docs/for-users/submit-sequences.md | 14 +++- .../content/docs/reference/fasta-format.md | 19 +++-- ingest/scripts/heuristic_group_segments.py | 10 +-- .../expected_output_cchf/revise_metadata.tsv | 2 +- .../expected_output_cchf/submit_metadata.tsv | 2 +- .../tests/specs/cli/end-to-end-flow.spec.ts | 2 +- .../tests/test-data/cchfv_test_metadata.tsv | 2 +- .../test-data/cchfv_test_metadata.tsv.zst | Bin 899 -> 899 bytes website/src/components/Edit/EditPage.tsx | 4 +- website/src/components/Edit/MetadataForm.tsx | 10 +-- website/src/components/Edit/SequencesForm.tsx | 4 +- .../SequenceEntryUploadComponent.tsx | 4 +- .../Submission/FormOrUploadWrapper.tsx | 4 +- website/src/config.ts | 78 +++++++++++++----- .../[organism]/metadata-overview/index.ts | 17 ++-- website/src/settings.ts | 2 +- website/src/types/config.ts | 2 +- 23 files changed, 131 insertions(+), 81 deletions(-) diff --git a/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionControllerDescriptions.kt b/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionControllerDescriptions.kt index fa4fb59521..b8be9826f7 100644 --- a/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionControllerDescriptions.kt +++ b/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionControllerDescriptions.kt @@ -1,7 +1,7 @@ package org.loculus.backend.controller -import org.loculus.backend.model.FASTA_ID_HEADER -import org.loculus.backend.model.FASTA_ID_SEPARATOR +import org.loculus.backend.model.FASTA_IDS_HEADER +import org.loculus.backend.model.FASTA_IDS_SEPARATOR import org.loculus.backend.model.METADATA_ID_HEADER const val SUBMIT_RESPONSE_DESCRIPTION = """ @@ -30,7 +30,7 @@ The file may be compressed with zstd, xz, zip, gzip, lzma, bzip2 (with common ex If the underlying organism has a single segment, the headers of the fasta file must match the '$METADATA_ID_HEADER' field in the metadata file. If the underlying organism has multiple segments, -the headers of the fasta file must be added in a '$FASTA_ID_SEPARATOR'-separated list to the '$FASTA_ID_HEADER' +the headers of the fasta file must be added in a '$FASTA_IDS_SEPARATOR'-separated list to the '$FASTA_IDS_HEADER' field in the metadata file. """ diff --git a/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt b/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt index c146b26a9c..7f667d81e6 100644 --- a/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt +++ b/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt @@ -33,8 +33,8 @@ import java.io.InputStream const val METADATA_ID_HEADER = "id" const val METADATA_ID_HEADER_ALTERNATE_FOR_BACKCOMPAT = "submissionId" -const val FASTA_ID_HEADER = "fastaId" -const val FASTA_ID_SEPARATOR = " " +const val FASTA_IDS_HEADER = "fastaIds" +const val FASTA_IDS_SEPARATOR = " " const val ACCESSION_HEADER = "accession" private val log = KotlinLogging.logger { } diff --git a/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt b/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt index 128841a60b..d201d4a045 100644 --- a/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt +++ b/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt @@ -5,8 +5,8 @@ import org.apache.commons.csv.CSVFormat import org.apache.commons.csv.CSVRecord import org.loculus.backend.controller.UnprocessableEntityException import org.loculus.backend.model.ACCESSION_HEADER -import org.loculus.backend.model.FASTA_ID_HEADER -import org.loculus.backend.model.FASTA_ID_SEPARATOR +import org.loculus.backend.model.FASTA_IDS_HEADER +import org.loculus.backend.model.FASTA_IDS_SEPARATOR import org.loculus.backend.model.FastaId import org.loculus.backend.model.METADATA_ID_HEADER import org.loculus.backend.model.METADATA_ID_HEADER_ALTERNATE_FOR_BACKCOMPAT @@ -46,16 +46,16 @@ fun findAndValidateSubmissionIdHeader(headerNames: List): String { fun extractFastaIdsFromRecord(record: CSVRecord, submissionId: String, recordNumber: Int): List { val headerNames = record.parser.headerNames - return when (headerNames.contains(FASTA_ID_HEADER)) { + return when (headerNames.contains(FASTA_IDS_HEADER)) { true -> { - val fastaIdValues = record[FASTA_ID_HEADER] + val fastaIdValues = record[FASTA_IDS_HEADER] if (fastaIdValues.isNullOrEmpty()) { throw UnprocessableEntityException( - "In metadata file: record #$recordNumber: column `$FASTA_ID_HEADER` is empty. This is invalid. Full record: $record", + "In metadata file: record #$recordNumber: column `$FASTA_IDS_HEADER` is empty. This is invalid. Full record: $record", ) } - fastaIdValues.split(Regex(FASTA_ID_SEPARATOR)) + fastaIdValues.split(Regex(FASTA_IDS_SEPARATOR)) .map { it.trim() } .filter { it.isNotEmpty() } } @@ -100,7 +100,7 @@ fun metadataEntryStreamAsSequence(metadataInputStream: InputStream): Sequenceseq_12` has fasta ID `seq_12`. +- Metadata for each sample with a unique `id`. + - When uploading through the API, only `tsv` is supported. + - When uploading through the website, `xlsx` files are also accepted. + - Each organism has its own metadata template available on the submission page. + - On the website, you can map columns from your file to the expected metadata fields using the **Add column mapping** option. + +Loculus matches metadata and sequences using the `id` column in the metadata (i.e. the sequence with fasta ID `seq_12` will be joined with the metadata entry with `id` of `seq_12`). For multi-segmented pathogens, you can provide an additional metadata field called `fastaIds` containing a space-separated list of fasta IDs to link multiple sequences to a single submission, e.g. `seq_12_A seq_12_B`. ![Metadata template.](../../../assets/MetadataTemplate.png) ### Multi-segmented Pathogens -Loculus expects multi-segmented pathogens to have one unique ID per **isolate** (pathogen sample containing all segments). However, `fasta` files should still have a separate entry/record per segment. Therefore, each record's FASTA id should include the unique ID of the isolate and the segment name, for example: `ID + '_' + segmentName`. The metadata is uploaded per isolate, i.e. there will be only one row for each `ID` and segmented metadata parameters need to be uploaded individually, i.e. under `length_{segmentName}` etc. +Multi-segmented pathogens must have one unique `id` per **isolate** (i.e. one per pathogen sample containing all segments). Each segment will be a unique entry in the FASTA file with its own FASTA ID. Metadata is uploaded per isolate, meaning there will be a single metadata row per `id`. This row should include a `fastaIds` field listing all segment fasta IDs, separated by spaces. ### Website diff --git a/docs/src/content/docs/reference/fasta-format.md b/docs/src/content/docs/reference/fasta-format.md index d2c2b0f0cc..4450f68e28 100644 --- a/docs/src/content/docs/reference/fasta-format.md +++ b/docs/src/content/docs/reference/fasta-format.md @@ -2,10 +2,9 @@ title: FASTA format --- -[_FASTA_](https://en.wikipedia.org/wiki/FASTA_format) is a file format to store sequence data with some additional metadata. -Loculus provides sequence data for download and expects sequence data in the FASTA format when submitting sequences. +The [_FASTA_](https://en.wikipedia.org/wiki/FASTA_format) format is a standard way to store sequence data along with optional metadata. Loculus provides sequence data in FASTA files and expects FASTA-formatted input when sequences are submitted. -The metadata is given in a line starting with the `>` character. Example: +Each sequence entry begins with a metadata line starting with the `>` character. For example: ``` >TTKC257461 2021-05-12, Congo @@ -13,9 +12,17 @@ TTATGCTTCGTAAAATGTAGGTCTTGAACCAAACATTCTTTGAAAAAATGAGATGCATAA AACTTTATTATCCAATAGATTAACTATTTCAGACGTCAATCGTTTAAAGTAAACTTCGTA ``` -The part immediately following the `>` and up to the first space or end of the line is the _ID_ of the sequence or segment that follows this line. In the example above, the ID is `TTKC257461`. +The text immediately following `>` and extending to the first space (or the end of the line) is the _ID_ of the sequence or segment. In the example above, the ID is `TTKC257461`. -When dealing with multi-segment isolates, Loculus expects all segments to have the same ID, but there is still a separate entry per segment. The segment ID is the `ID + '_' + segmentName`, for example: +For isolates composed of multiple segments, Loculus requires one metadata entry per sample, and every segment must appear as a separate sequence in the uploaded FASTA file. + +The metadata file should include a field named `fastaIds`, containing a space-separated list of all FASTA IDs associated with that sample. For example, if the following three sequences correspond to the metadata entry NIPAK-sample, the `fastaIds` should be: + +``` +test_NIHPAK-19_L test_NIHPAK-19_M test_NIHPAK-19_S +``` + +Example sequences: ``` >test_NIHPAK-19_L @@ -26,6 +33,4 @@ GTGGATTGAGCATCTTAATTGCAGCATACTTGTCAACATCATGCATATATCATTGATGTATGCAGTTTTCTGCTTGCAGC GTGTTCTCTTGAGTGTTGGCAAAATGGAAAACAAAATCGAGGTGAACAACAAAGATGAGATGAACAAATGGTTTGAGGAGTTCAAGAAAGGAAATGGACTTGTGGACACTTTCACAAACTCNTATTCCTTTTGTGAAAGCGTNCCAAATCTGGACAGNTTTGTNAATGGAGAAAAGACATAGGCTTCCGTGTCA ``` -Here, three segments are given named `L`, `M` and `S`, and the ID for the whole sequence is `test_NIHPAK`. - A segment cannot be empty. diff --git a/ingest/scripts/heuristic_group_segments.py b/ingest/scripts/heuristic_group_segments.py index 27d747af52..d47c57992c 100644 --- a/ingest/scripts/heuristic_group_segments.py +++ b/ingest/scripts/heuristic_group_segments.py @@ -30,7 +30,7 @@ import orjsonl import yaml -FASTA_ID_SEPARATOR = " " +FASTA_IDS_SEPARATOR = " " def sort_authors(authors: str) -> str: @@ -213,7 +213,7 @@ def main( if segment in group ] ) - segments_list_str = FASTA_ID_SEPARATOR.join([ + segments_list_str = FASTA_IDS_SEPARATOR.join([ f"{joint_key}_{segment}" for segment in config.nucleotide_sequences if segment in group @@ -240,16 +240,16 @@ def main( ) row["id"] = joint_key - row["fastaId"] = segments_list_str + row["fastaIds"] = segments_list_str # Hash of all metadata fields should be the same if # 1. field is not in keys_to_keep and # 2. field is in keys_to_keep but is "" or None filtered_record = {k: str(v) for k, v in row.items() if v is not None and str(v)} - # rename "id" to "submissionId" and ignore fastaId for back-compatibility with old hashes + # rename "id" to "submissionId" and ignore fastaIds for back-compatibility with old hashes filtered_record["submissionId"] = filtered_record.pop("id") - filtered_record.pop("fastaId", None) + filtered_record.pop("fastaIds", None) row["hash"] = hashlib.md5( json.dumps(filtered_record, sort_keys=True).encode(), usedforsecurity=False diff --git a/ingest/tests/expected_output_cchf/revise_metadata.tsv b/ingest/tests/expected_output_cchf/revise_metadata.tsv index 91adc61cbd..0ff9b3cd63 100644 --- a/ingest/tests/expected_output_cchf/revise_metadata.tsv +++ b/ingest/tests/expected_output_cchf/revise_metadata.tsv @@ -1,2 +1,2 @@ -authorAffiliations authors bioprojectAccession biosampleAccession geoLocAdmin1 geoLocCountry hostNameScientific hostTaxonId isLabHost ncbiReleaseDate ncbiSourceDb ncbiVirusName ncbiVirusTaxId sampleCollectionDate specimenCollectorSampleId ncbiUpdateDate_L ncbiUpdateDate_M ncbiUpdateDate_S insdcVersion_L insdcVersion_M insdcVersion_S insdcRawReadsAccession_L insdcRawReadsAccession_M insdcRawReadsAccession_S hash_L hash_M hash_S insdcAccessionBase_L insdcAccessionBase_M insdcAccessionBase_S insdcAccessionFull_L insdcAccessionFull_M insdcAccessionFull_S id hash accession fastaId +authorAffiliations authors bioprojectAccession biosampleAccession geoLocAdmin1 geoLocCountry hostNameScientific hostTaxonId isLabHost ncbiReleaseDate ncbiSourceDb ncbiVirusName ncbiVirusTaxId sampleCollectionDate specimenCollectorSampleId ncbiUpdateDate_L ncbiUpdateDate_M ncbiUpdateDate_S insdcVersion_L insdcVersion_M insdcVersion_S insdcRawReadsAccession_L insdcRawReadsAccession_M insdcRawReadsAccession_S hash_L hash_M hash_S insdcAccessionBase_L insdcAccessionBase_M insdcAccessionBase_S insdcAccessionFull_L insdcAccessionFull_M insdcAccessionFull_S id hash accession fastaIds Public Health England, Research Deryabin, ; Atshabar, B.; Sansyzbaev, Y.; Berezin, V.; Nurmakhanov, T.; Yeskhojayev, O.; Vilkova, A.; Shevtsov, A.; Hewson, R.; Atkinson, B. Sairam district Kazakhstan Hyalomma anatolicum 176092 2016-04-30T00:00:00Z GenBank Orthonairovirus haemorrhagiae 3052518 2015 tick pool #134 2016-04-30T00:00:00Z 1 4f8a46f4b233b3d9f05d5336b8b711aa KX096703 KX096703.1 KX096703.1.S c7d098a014a36e8b8f87c73621b7d6fc LOC_0000VXA KX096703.1.S_S diff --git a/ingest/tests/expected_output_cchf/submit_metadata.tsv b/ingest/tests/expected_output_cchf/submit_metadata.tsv index 8e888f76fb..8eec2de672 100644 --- a/ingest/tests/expected_output_cchf/submit_metadata.tsv +++ b/ingest/tests/expected_output_cchf/submit_metadata.tsv @@ -1,2 +1,2 @@ -authorAffiliations authors bioprojectAccession biosampleAccession geoLocAdmin1 geoLocCountry hostNameScientific hostTaxonId isLabHost ncbiReleaseDate ncbiSourceDb ncbiVirusName ncbiVirusTaxId sampleCollectionDate specimenCollectorSampleId ncbiUpdateDate_L ncbiUpdateDate_M ncbiUpdateDate_S insdcVersion_L insdcVersion_M insdcVersion_S insdcRawReadsAccession_L insdcRawReadsAccession_M insdcRawReadsAccession_S hash_L hash_M hash_S insdcAccessionBase_L insdcAccessionBase_M insdcAccessionBase_S insdcAccessionFull_L insdcAccessionFull_M insdcAccessionFull_S id hash fastaId +authorAffiliations authors bioprojectAccession biosampleAccession geoLocAdmin1 geoLocCountry hostNameScientific hostTaxonId isLabHost ncbiReleaseDate ncbiSourceDb ncbiVirusName ncbiVirusTaxId sampleCollectionDate specimenCollectorSampleId ncbiUpdateDate_L ncbiUpdateDate_M ncbiUpdateDate_S insdcVersion_L insdcVersion_M insdcVersion_S insdcRawReadsAccession_L insdcRawReadsAccession_M insdcRawReadsAccession_S hash_L hash_M hash_S insdcAccessionBase_L insdcAccessionBase_M insdcAccessionBase_S insdcAccessionFull_L insdcAccessionFull_M insdcAccessionFull_S id hash fastaIds Chumakov Institute of Poliomyelitis and Viral Encephalitides Lukashev, A. N.; Klimentov, A. S.; Smirnova, S. E.; Dzagurova, T. K.; Drexler, J. F.; Gmyl, A. P. Uganda Homo sapiens 9606 2016-12-07T00:00:00Z GenBank Orthonairovirus haemorrhagiae 3052518 1958 Nakiwogo 2016-12-07T00:00:00Z 2016-12-07T00:00:00Z 1 1 7b10a4e21daa8a2e693958761be17d53 70954bc35782b5592858ac3f1a6bbf89 KX013483 KX013485 KX013483.1 KX013485.1 KX013483.1.L/KX013485.1.S bb3a6d8df47cb2891e7b60030a40c335 KX013483.1.L/KX013485.1.S_L KX013483.1.L/KX013485.1.S_S diff --git a/integration-tests/tests/specs/cli/end-to-end-flow.spec.ts b/integration-tests/tests/specs/cli/end-to-end-flow.spec.ts index 475b9b3e63..c867b0ebb9 100644 --- a/integration-tests/tests/specs/cli/end-to-end-flow.spec.ts +++ b/integration-tests/tests/specs/cli/end-to-end-flow.spec.ts @@ -17,7 +17,7 @@ cliTest.describe('CLI End-to-End Submission Flow', () => { const submissionId1 = `cli_e2e_${timestamp}_001`; const submissionId2 = `cli_e2e_${timestamp}_002`; - const testMetadata = `authorAffiliations\tauthors\tgeoLocCountry\thostNameScientific\thostTaxonId\tsampleCollectionDate\tspecimenCollectorSampleId\tsubmissionId\tfastaId + const testMetadata = `authorAffiliations\tauthors\tgeoLocCountry\thostNameScientific\thostTaxonId\tsampleCollectionDate\tspecimenCollectorSampleId\tsubmissionId\tfastaIds "National Institute of Health, Department of Virology"\t"Ammar, M.; Salman, M.; Umair, M.; Ali, Q.; Hakim, R.; Haider, S.A.; Jamal, Z."\tPakistan\tHomo sapiens\t9606\t2023-08-26\tCCHF/NIHPAK-19/2023\t${submissionId1}\t${submissionId1}_L ${submissionId1}_M ${submissionId1}_S "Research Lab, University of Example"\t"Example, A.; Test, B.; Sample, C."\tColombia\tHomo sapiens\t9606\t2021-12-12\tXF499\t${submissionId2}\t${submissionId2}_L ${submissionId2}_M ${submissionId2}_S`; diff --git a/integration-tests/tests/test-data/cchfv_test_metadata.tsv b/integration-tests/tests/test-data/cchfv_test_metadata.tsv index 05be5a01e3..1c7113c32e 100644 --- a/integration-tests/tests/test-data/cchfv_test_metadata.tsv +++ b/integration-tests/tests/test-data/cchfv_test_metadata.tsv @@ -1,2 +1,2 @@ -fastaId ampliconPcrPrimerScheme ampliconSize anatomicalMaterial anatomicalPart authorAffiliations authors bodyProduct breadthOfCoverage cellLine collectionDevice collectionMethod consensusSequenceSoftwareName consensusSequenceSoftwareVersion cultureId dehostingMethod depthOfCoverage diagnostic_measurement_method diagnosticMeasurementUnit diagnosticMeasurementValue diagnosticTargetGeneName diagnosticTargetPresence environmentalMaterial environmentalSite experimentalSpecimenRoleType exposureDetails exposureEvent exposureSetting foodProduct foodProductProperties geoLocAdmin1 geoLocAdmin2 geoLocCity geoLocCountry geoLocSite hostAge hostAgeBin hostDisease hostGender hostHealthOutcome hostHealthState hostNameCommon hostNameScientific hostOriginCountry hostRole hostTaxonId hostVaccinationStatus passageMethod passageNumber presamplingActivity previousInfectionDisease previousInfectionOrganism purposeOfSampling purposeOfSequencing qualityControlDetails qualityControlDetermination qualityControlIssues qualityControlMethodName qualityControlMethodVersion rawSequenceDataProcessingMethod referenceGenomeAccession sampleCollectionDate sampleReceivedDate sampleType sequencedByContactEmail sequencedByContactName sequencedByOrganization sequencingAssayType sequencingDate sequencingInstrument sequencingProtocol signsAndSymptoms specimenCollectorSampleId specimenProcessing specimenProcessingDetails bioprojectAccessions submissionId travelHistory versionComment +fastaIds ampliconPcrPrimerScheme ampliconSize anatomicalMaterial anatomicalPart authorAffiliations authors bodyProduct breadthOfCoverage cellLine collectionDevice collectionMethod consensusSequenceSoftwareName consensusSequenceSoftwareVersion cultureId dehostingMethod depthOfCoverage diagnostic_measurement_method diagnosticMeasurementUnit diagnosticMeasurementValue diagnosticTargetGeneName diagnosticTargetPresence environmentalMaterial environmentalSite experimentalSpecimenRoleType exposureDetails exposureEvent exposureSetting foodProduct foodProductProperties geoLocAdmin1 geoLocAdmin2 geoLocCity geoLocCountry geoLocSite hostAge hostAgeBin hostDisease hostGender hostHealthOutcome hostHealthState hostNameCommon hostNameScientific hostOriginCountry hostRole hostTaxonId hostVaccinationStatus passageMethod passageNumber presamplingActivity previousInfectionDisease previousInfectionOrganism purposeOfSampling purposeOfSequencing qualityControlDetails qualityControlDetermination qualityControlIssues qualityControlMethodName qualityControlMethodVersion rawSequenceDataProcessingMethod referenceGenomeAccession sampleCollectionDate sampleReceivedDate sampleType sequencedByContactEmail sequencedByContactName sequencedByOrganization sequencingAssayType sequencingDate sequencingInstrument sequencingProtocol signsAndSymptoms specimenCollectorSampleId specimenProcessing specimenProcessingDetails bioprojectAccessions submissionId travelHistory versionComment test_NIHPAK-19_L test_NIHPAK-19_M test_NIHPAK-19_S "National Institute of Health, Department of Virology" "Ammar, M.; Salman, M.; Umair, M.; Ali, Q.; Hakim, R.; Haider, S.A.; Jamal, Z." Hogwarts Pakistan Homo sapiens 9606 2023-08-26 CCHF/NIHPAK-19/2023 test_NIHPAK-19 "OR964915.1,OR964926.1,OR964937.1" diff --git a/integration-tests/tests/test-data/cchfv_test_metadata.tsv.zst b/integration-tests/tests/test-data/cchfv_test_metadata.tsv.zst index 44add209705396fc34851824dca3bc754da8eb14..cc12938c46e6854bfa7c14d7ea843bdb9a9fb5bd 100644 GIT binary patch delta 423 zcmV;Y0a*Tn2ZIL-D77#BWH1J;8vw?U3p;-oYb8`RC6iX)Ig?B=O7{EqmReTtv`B{& z**M`NhH`B_2?_c0OiBU{Wd()OWoAL0PW#g9vb?lS4wW+szGD;^_a=W* zLU){$zty$Sf>D+!V8yDWZCM)WZ|Z`Uo#O~T!?3aRoHT5i>BD$l-b zMtn+*6|bSi>pG~oi{EtF&oNzU)BJy~la?CnSB#<#^IZ9_gup$qVwzpKZmthT3$~&-i+vsvfXoWU+_^f0Xv^N z#dS9BUyf{E8$Jn2T7@D@I#2y39uAYk0hR%p RvswZn0SK|Cg&Z7VT delta 423 zcmV;Y0a*Tn2ZIL-D77#BWG@D-8vwSE3p;=AVy%SArexCUJ7ac_Sj zC3MG0`CDBJEf{5)0#>X_+Lon}{-!Qy**T8jLyp%!OXeHqq?2t%IHs#)6@m2h@8ihk zH3E68I`%A<;FOf7e&@t}Va$>Ggnm<#z?>_+!;%Pa6I8NbA{?>Hq>T9uAYk0hR#) RvswZn0SMo5{s9^vw3EbD(8d4& diff --git a/website/src/components/Edit/EditPage.tsx b/website/src/components/Edit/EditPage.tsx index 812c1e657e..d881353a70 100644 --- a/website/src/components/Edit/EditPage.tsx +++ b/website/src/components/Edit/EditPage.tsx @@ -77,11 +77,11 @@ const InnerEditPage: FC = ({ const submitEditedDataForAccessionVersion = () => { if (isCreatingRevision) { - const fastaId = submissionDataTypes.consensusSequences ? editableSequences.getFastaIds() : undefined; + const fastaIds = submissionDataTypes.consensusSequences ? editableSequences.getFastaIds() : undefined; const metadataFile = editableMetadata.getMetadataTsv( dataToEdit.submissionId, dataToEdit.accession, - fastaId, + fastaIds, ); if (metadataFile === undefined) { toast.error('Please enter metadata.', { position: 'top-center', autoClose: false }); diff --git a/website/src/components/Edit/MetadataForm.tsx b/website/src/components/Edit/MetadataForm.tsx index 967a2c902e..6f7a9470a4 100644 --- a/website/src/components/Edit/MetadataForm.tsx +++ b/website/src/components/Edit/MetadataForm.tsx @@ -4,7 +4,7 @@ import { Fragment, type Dispatch, type FC, type SetStateAction } from 'react'; import { EditableDataRow } from './DataRow.tsx'; import type { Row } from './InputField'; -import { ACCESSION_FIELD, FASTA_ID_FIELD, SUBMISSION_ID_INPUT_FIELD } from '../../settings.ts'; +import { ACCESSION_FIELD, FASTA_IDS_FIELD, SUBMISSION_ID_INPUT_FIELD } from '../../settings.ts'; import { mapErrorsAndWarnings, type SequenceEntryToEdit } from '../../types/backend.ts'; import type { InputField } from '../../types/config'; @@ -71,9 +71,9 @@ export class EditableMetadata { * @param submissionId optional (might already be in the rows if add to the form initially). * The submission ID to put into the TSV. * @param accession optional. If an accession is already assigned to this sequence, it should be given. - * @param fastaId optional. Add a fastaId field with content if supplied. + * @param fastaIds optional. Add a fastaIds field with content if supplied. */ - getMetadataTsv(submissionId?: string, accession?: string, fastaId?: string): File | undefined { + getMetadataTsv(submissionId?: string, accession?: string, fastaIds?: string): File | undefined { // if no values are set at all, return undefined if (!this.rows.some((row) => row.value !== '')) return undefined; @@ -94,8 +94,8 @@ export class EditableMetadata { tsvFields.set(ACCESSION_FIELD, accession); } - if (fastaId) { - tsvFields.set(FASTA_ID_FIELD, fastaId); + if (fastaIds) { + tsvFields.set(FASTA_IDS_FIELD, fastaIds); } const tsvContent = Papa.unparse([Array.from(tsvFields.keys()), Array.from(tsvFields.values())], { diff --git a/website/src/components/Edit/SequencesForm.tsx b/website/src/components/Edit/SequencesForm.tsx index b568bd84bc..b981c4b3dc 100644 --- a/website/src/components/Edit/SequencesForm.tsx +++ b/website/src/components/Edit/SequencesForm.tsx @@ -2,7 +2,7 @@ import { type Dispatch, type FC, type SetStateAction } from 'react'; import { toast } from 'react-toastify'; import { type SequenceEntryToEdit } from '../../types/backend.ts'; -import { FASTA_ID_SEPARATOR } from '../../types/config.ts'; +import { FASTA_IDS_SEPARATOR } from '../../types/config.ts'; import type { ReferenceGenomesLightweightSchema } from '../../types/referencesGenomes.ts'; import { FileUploadComponent } from '../Submission/FileUpload/FileUploadComponent.tsx'; import { PLAIN_SEGMENT_KIND, VirtualPlainSegmentFile } from '../Submission/FileUpload/fileProcessing.ts'; @@ -160,7 +160,7 @@ export class EditableSequences { getFastaIds(): string { const filledRows = this.rows.filter((row) => row.value !== null); - return filledRows.map((sequence) => sequence.fastaHeader).join(FASTA_ID_SEPARATOR); + return filledRows.map((sequence) => sequence.fastaHeader).join(FASTA_IDS_SEPARATOR); } getSequenceFasta(): File | undefined { diff --git a/website/src/components/Submission/FileUpload/SequenceEntryUploadComponent.tsx b/website/src/components/Submission/FileUpload/SequenceEntryUploadComponent.tsx index c2e94f12bf..2a719c6029 100644 --- a/website/src/components/Submission/FileUpload/SequenceEntryUploadComponent.tsx +++ b/website/src/components/Submission/FileUpload/SequenceEntryUploadComponent.tsx @@ -95,8 +95,8 @@ export const SequenceEntryUpload: FC = ({ {isMultiSegmented && (

{organism.toUpperCase()} has a multi-segmented genome. Please submit one metadata entry with a - unique submissionId column for the full multi-segmented sample, e.g. sample1 and a{' '} - fastaId column with a space-separated list of the fasta headers of all segments, e.g.{' '} + unique id column for the full multi-segmented sample, e.g. sample1 and a{' '} + fastaIds column with a space-separated list of the fasta headers of all segments, e.g.{' '} fastaHeaderSegment1 fastaHeaderSegment2 fastaHeaderSegment3.

)} diff --git a/website/src/components/Submission/FormOrUploadWrapper.tsx b/website/src/components/Submission/FormOrUploadWrapper.tsx index 5c044f60e4..25eb2e8df6 100644 --- a/website/src/components/Submission/FormOrUploadWrapper.tsx +++ b/website/src/components/Submission/FormOrUploadWrapper.tsx @@ -85,8 +85,8 @@ export const FormOrUploadWrapper: FC = ({ if (!submissionId) { return { type: 'error', errorMessage: 'Please specify an ID.' }; } - const fastaId = enableConsensusSequences ? editableSequences.getFastaIds() : undefined; - const metadataFile = editableMetadata.getMetadataTsv(undefined, undefined, fastaId); + const fastaIds = enableConsensusSequences ? editableSequences.getFastaIds() : undefined; + const metadataFile = editableMetadata.getMetadataTsv(undefined, undefined, fastaIds); if (!metadataFile) { return { type: 'error', errorMessage: 'Please specify metadata.' }; } diff --git a/website/src/config.ts b/website/src/config.ts index 2aa352f2d3..b60ff003b0 100644 --- a/website/src/config.ts +++ b/website/src/config.ts @@ -3,7 +3,7 @@ import path from 'path'; import type { z, ZodError } from 'zod'; -import { ACCESSION_FIELD, SUBMISSION_ID_INPUT_FIELD } from './settings.ts'; +import { ACCESSION_FIELD, FASTA_IDS_FIELD, SUBMISSION_ID_INPUT_FIELD } from './settings.ts'; import { type InputField, type InstanceConfig, @@ -160,8 +160,10 @@ export function getMetadataTemplateFields( ): Map { const schema = getConfig(organism).schema; const baseFields: string[] = schema.metadataTemplate ?? schema.inputFields.map((field) => field.name); - const extraFields = - action === 'submit' ? [SUBMISSION_ID_INPUT_FIELD] : [ACCESSION_FIELD, SUBMISSION_ID_INPUT_FIELD]; + const submissionIdInputFields = getSubmissionIdInputFields(isMultiSegmentedOrganism(organism)).map( + (field) => field.name, + ); + const extraFields = action === 'submit' ? submissionIdInputFields : [ACCESSION_FIELD, ...submissionIdInputFields]; const allFields = [...extraFields, ...baseFields]; const fieldsToDisplaynames = new Map( allFields.map((field) => [field, schema.metadata.find((metadata) => metadata.name === field)?.displayName]), @@ -182,17 +184,48 @@ function getAccessionInputField(): InputField { }; } -function getSubmissionIdInputField(): InputField { - return { - name: SUBMISSION_ID_INPUT_FIELD, - displayName: 'ID', - definition: 'FASTA ID', - guidance: - 'Your sequence identifier; should match the FASTA file header - this is used to link the metadata to the FASTA sequence', - example: 'GJP123', - noEdit: true, - required: true, - }; +export function getSubmissionIdInputFields(isMultiSegmented: boolean): InputField[] { + if (!isMultiSegmented) { + return [ + { + name: SUBMISSION_ID_INPUT_FIELD, + displayName: 'ID', + definition: 'FASTA ID', + guidance: + 'Your sequence identifier; should match the FASTA file header - this is used to link the metadata to the FASTA sequence.', + example: 'GJP123', + noEdit: true, + required: true, + }, + ]; + } + return [ + { + name: SUBMISSION_ID_INPUT_FIELD, + displayName: 'ID', + definition: 'METADATA ID', + guidance: + 'Your sample identifier. If no column with FASTA IDS is provided, this ID will be used to associate the metadata with the sequence.', + example: 'GJP123', + noEdit: true, + required: true, + }, + { + name: FASTA_IDS_FIELD, + displayName: 'FASTA IDS', + definition: 'FASTA IDS', + guidance: 'Space-separated list of FASTA IDS of each sequence to be associated with this metadata entry.', + example: 'GJP123 GJP124', + noEdit: true, + desired: true, + }, + ]; +} + +export function isMultiSegmentedOrganism(organism: string): boolean { + const referenceGenomes = getReferenceGenome(organism); + const segmentNames = referenceGenomes.nucleotideSequences.map((s) => s.name); + return segmentNames.length > 1; } export function getGroupedInputFields( @@ -201,20 +234,23 @@ export function getGroupedInputFields( excludeDuplicates: boolean = false, ): Map { const inputFields = getConfig(organism).schema.inputFields; + const isMultiSegmented = isMultiSegmentedOrganism(organism); const metadata = getConfig(organism).schema.metadata; const groups = new Map(); - const requiredFields = inputFields.filter((meta) => meta.required); - const desiredFields = inputFields.filter((meta) => meta.desired); - const coreFields = - action === 'submit' ? [getSubmissionIdInputField()] : [getSubmissionIdInputField(), getAccessionInputField()]; + action === 'submit' + ? getSubmissionIdInputFields(isMultiSegmented) + : getSubmissionIdInputFields(isMultiSegmented).concat(getAccessionInputField()); - groups.set('Required fields', [...coreFields, ...requiredFields]); - groups.set('Desired fields', desiredFields); - if (!excludeDuplicates) groups.set('Submission details', [getSubmissionIdInputField()]); + const allFields = [...coreFields, ...inputFields]; + const requiredFields = allFields.filter((meta) => meta.required); + const desiredFields = allFields.filter((meta) => meta.desired); + groups.set('Required fields', requiredFields); + groups.set('Desired fields', desiredFields); + if (!excludeDuplicates) groups.set('Submission details', getSubmissionIdInputFields(isMultiSegmented)); const fieldAlreadyAdded = (fieldName: string) => Array.from(groups.values()) .flatMap((fields) => fields.map((f) => f.name)) diff --git a/website/src/pages/[organism]/metadata-overview/index.ts b/website/src/pages/[organism]/metadata-overview/index.ts index 71bafc4b76..9d4d93a543 100644 --- a/website/src/pages/[organism]/metadata-overview/index.ts +++ b/website/src/pages/[organism]/metadata-overview/index.ts @@ -1,8 +1,12 @@ import type { APIRoute } from 'astro'; import { cleanOrganism } from '../../../components/Navigation/cleanOrganism.ts'; -import { getSchema } from '../../../config.ts'; -import { SUBMISSION_ID_INPUT_FIELD } from '../../../settings.ts'; +import { getSchema, getSubmissionIdInputFields, isMultiSegmentedOrganism } from '../../../config.ts'; +import type { InputField } from '../../../types/config.ts'; + +function createRowFromField(field: InputField): string { + return `${field.name}\t${field.required ?? ''}\t${field.definition ?? ''} ${field.guidance ?? ''}\t${field.example ?? ''}`; +} export const GET: APIRoute = ({ params }) => { const rawOrganism = params.organism!; @@ -13,7 +17,9 @@ export const GET: APIRoute = ({ params }) => { }); } - const extraFields = [SUBMISSION_ID_INPUT_FIELD]; + const extraFields = getSubmissionIdInputFields(isMultiSegmentedOrganism(organism.key)).map((field) => + createRowFromField(field), + ); const tableHeader = 'Field Name\tRequired\tDefinition\tGuidance\tExample'; @@ -26,10 +32,7 @@ export const GET: APIRoute = ({ params }) => { const filename = `${organism.displayName.replaceAll(' ', '_')}_metadata_overview.tsv`; headers['Content-Disposition'] = `attachment; filename="${filename}"`; - const fieldNames = inputFields.map( - (field) => - `${field.name}\t${field.required ?? ''}\t${field.definition ?? ''} ${field.guidance ?? ''}\t${field.example ?? ''}`, - ); + const fieldNames = inputFields.map((field) => createRowFromField(field)); const tsvTemplate = [tableHeader, ...extraFields, ...fieldNames].join('\n'); return new Response(tsvTemplate, { diff --git a/website/src/settings.ts b/website/src/settings.ts index 829b7664c7..0e235a518c 100644 --- a/website/src/settings.ts +++ b/website/src/settings.ts @@ -2,7 +2,7 @@ export const pageSize = 100; export const ACCESSION_VERSION_FIELD = 'accessionVersion'; export const ACCESSION_FIELD = 'accession'; -export const FASTA_ID_FIELD = 'fastaId'; +export const FASTA_IDS_FIELD = 'fastaIds'; export const VERSION_FIELD = 'version'; export const VERSION_STATUS_FIELD = 'versionStatus'; export const IS_REVOCATION_FIELD = 'isRevocation'; diff --git a/website/src/types/config.ts b/website/src/types/config.ts index bfe7d9316b..47ea24b295 100644 --- a/website/src/types/config.ts +++ b/website/src/types/config.ts @@ -3,7 +3,7 @@ import z from 'zod'; import { mutationProportionCount, orderDirection } from './lapis.ts'; import { referenceGenomes } from './referencesGenomes.ts'; -export const FASTA_ID_SEPARATOR = ' '; +export const FASTA_IDS_SEPARATOR = ' '; // These metadata types need to be kept in sync with the backend config class `MetadataType` in Config.kt export const metadataPossibleTypes = z.enum([ From 3b78a3029ed52f6bdbc4c38c10b4e0da956e2c19 Mon Sep 17 00:00:00 2001 From: "Anna (Anya) Parker" <50943381+anna-parker@users.noreply.github.com> Date: Mon, 1 Dec 2025 16:02:40 +0100 Subject: [PATCH 30/44] feat(prepro): deduplicate segment assignment code and add tests (#5554) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit partially resolves https://github.com/loculus-project/loculus/issues/5576 ### Screenshot Have segment assignment by nextclade sort and segment assignment via nextclade align use the same code to assign segment for each entry after running nextclade on the batch. I also realized we dont have tests for the `require_nextclade_sort_match` config option - I added them here ### PR Checklist - [ ] All necessary documentation has been adapted. - [ ] The implemented feature is covered by appropriate, automated tests. - [ ] Any manual testing that has been done is documented (i.e. what exactly was tested?) 🚀 Preview: https://multipath-updates.loculus.org --- .../src/loculus_preprocessing/datatypes.py | 31 +- .../src/loculus_preprocessing/nextclade.py | 419 ++++++++---------- .../src/loculus_preprocessing/prepro.py | 6 +- .../ebola-sudan/minimizer/minimizer.json | 1 + .../nextclade/tests/multi_segment_config.yaml | 2 - .../tests/single_segment_config.yaml | 2 +- .../tests/test_nextclade_preprocessing.py | 125 +++++- 7 files changed, 309 insertions(+), 277 deletions(-) create mode 100644 preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/minimizer/minimizer.json diff --git a/preprocessing/nextclade/src/loculus_preprocessing/datatypes.py b/preprocessing/nextclade/src/loculus_preprocessing/datatypes.py index 8c196cd9ca..4a6d903cc6 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/datatypes.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/datatypes.py @@ -1,4 +1,3 @@ -from collections import defaultdict from collections.abc import Iterable from dataclasses import dataclass, field from enum import StrEnum, unique @@ -139,13 +138,12 @@ class Annotation: @dataclass -class Alerts: - errors: dict[AccessionVersion, list[ProcessingAnnotation]] = field( - default_factory=lambda: defaultdict(list) - ) - warnings: dict[AccessionVersion, list[ProcessingAnnotation]] = field( - default_factory=lambda: defaultdict(list) - ) +class Alert: + errors: list[ProcessingAnnotation] = field(default_factory=list) + warnings: list[ProcessingAnnotation] = field(default_factory=list) + + +Alerts = dict[AccessionVersion, Alert] @dataclass @@ -191,19 +189,22 @@ class SegmentClassificationMethod(StrEnum): @dataclass class SegmentAssignment: - unalignedNucleotideSequences: dict[SegmentName, NucleotideSequence | None] # noqa: N815 - sequenceNameToFastaId: dict[SegmentName, FastaId] # noqa: N815 - errors: list[ProcessingAnnotation] - warnings: list[ProcessingAnnotation] + unalignedNucleotideSequences: dict[SegmentName, NucleotideSequence | None] = field( + default_factory=dict + ) + sequenceNameToFastaId: dict[SegmentName, FastaId] = field(default_factory=dict) # noqa: N815 + alert: Alert = field(default_factory=Alert) @dataclass class SegmentAssignmentBatch: unalignedNucleotideSequences: dict[ AccessionVersion, dict[SegmentName, NucleotideSequence | None] - ] - sequenceNameToFastaId: dict[AccessionVersion, dict[SegmentName, FastaId]] # noqa: N815 - alerts: Alerts + ] = field(default_factory=dict) + sequenceNameToFastaId: dict[AccessionVersion, dict[SegmentName, FastaId]] = field( + default_factory=dict + ) + alerts: Alerts = field(default_factory=Alerts) @dataclass diff --git a/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py b/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py index e4a001f6d8..c6952b6952 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py @@ -17,6 +17,7 @@ from .config import AlignmentRequirement, Config, NextcladeSequenceAndDataset from .datatypes import ( AccessionVersion, + Alert, Alerts, AminoAcidInsertion, AminoAcidSequence, @@ -214,6 +215,7 @@ def accepted_sort_matches_or_default( return list(accepted_dataset_names) +# TODO: to be deprecated and multi-path feature used instead def check_nextclade_sort_matches( # noqa: PLR0913, PLR0917 result_file_dir: str, input_file: str, @@ -227,22 +229,18 @@ def check_nextclade_sort_matches( # noqa: PLR0913, PLR0917 - assert highest score is in sequence_and_dataset.accepted_sort_matches (default is nextclade_dataset_name) """ - result_file = result_file_dir + "/sort_output.tsv" df = run_sort( - result_file, + result_file_dir + "/sort_output.tsv", input_file, dataset_dir, ) hits = df.dropna(subset=["score"]).sort_values("score", ascending=False) best_hits = hits.groupby("seqName", as_index=False).first() - - all_ids = df["seqName"].unique() - hit_ids = best_hits["seqName"] - missing_ids = set(all_ids) - set(hit_ids) + missing_ids = set(df["seqName"].unique()) - set(best_hits["seqName"].unique()) for seq in missing_ids: - alerts.warnings[seq].append( + alerts[seq].warnings.append( sequence_annotation( "Sequence does not appear to match reference, per `nextclade sort`. " "Double check you are submitting to the correct organism." @@ -252,7 +250,7 @@ def check_nextclade_sort_matches( # noqa: PLR0913, PLR0917 for _, row in best_hits.iterrows(): # If best match is not the same as the dataset we are submitting to, add an error if row["dataset"] not in accepted_sort_matches: - alerts.errors[row["seqName"]].append( + alerts[row["seqName"]].errors.append( sequence_annotation( f"Sequence best matches {row['dataset']}, " "a different organism than the one you are submitting to: " @@ -266,30 +264,120 @@ def check_nextclade_sort_matches( # noqa: PLR0913, PLR0917 def write_nextclade_input_fasta( unprocessed: Sequence[UnprocessedEntry], input_file: str -) -> tuple[ - dict[AccessionVersion, dict[SegmentName, NucleotideSequence | None]], - defaultdict[str, tuple[AccessionVersion, FastaId]], -]: +) -> defaultdict[tuple[AccessionVersion, FastaId], str]: """ Write unprocessed sequences to a fasta file for nextclade input """ - input_unaligned_sequences: dict[ - AccessionVersion, dict[SegmentName, NucleotideSequence | None] - ] = defaultdict(dict) - id_map: defaultdict[str, tuple[AccessionVersion, FastaId]] = defaultdict( - lambda: (AccessionVersion(), FastaId()) - ) + id_map: defaultdict[tuple[AccessionVersion, FastaId], str] = defaultdict() os.makedirs(os.path.dirname(input_file), exist_ok=True) with open(input_file, "w", encoding="utf-8") as f: for entry in unprocessed: accession_version = entry.accessionVersion - input_unaligned_sequences[accession_version] = entry.data.unalignedNucleotideSequences - for fasta_id, seq in input_unaligned_sequences[accession_version].items(): + for fasta_id, seq in entry.data.unalignedNucleotideSequences.items(): id = f"{accession_version}__{fasta_id}" - id_map[id] = (accession_version, fasta_id) + id_map[accession_version, fasta_id] = id f.write(f">{id}\n") f.write(f"{seq}\n") - return input_unaligned_sequences, id_map + return id_map + + +def assign_segment( + entry: UnprocessedEntry, + id_map: dict[tuple[AccessionVersion, FastaId], str], + best_hits: pd.DataFrame, + config: Config, +) -> SegmentAssignment: + """ + Assign segments to sequences based on best hits from nextclade align or sort + 1. If no best hit for a sequence, add error/warning about missing segment + 2. If best hit does not match any accepted segment, add error/warning about missing segment + 3. If multiple sequences match the same segment, add error about duplicate segments + 4. If no sequences assigned and no errors about missing/duplicate segments, add error about + no sequence data found (e.g. when alignment requirement is ANY and all sequences miss) + """ + sort_results_map: dict[SegmentName, list[str]] = defaultdict(list) + segment_assignment = SegmentAssignment() + + has_missing_segments = False + has_duplicate_segments = False + + for fasta_id in entry.data.unalignedNucleotideSequences: + seq_id = id_map[entry.accessionVersion, fasta_id] + if seq_id not in best_hits["seqName"].unique(): + has_missing_segments = True + method = ( + "sort" + if config.segment_classification_method.value == "minimizer" + else config.segment_classification_method.value + ) + annotation = sequence_annotation( + f"Sequence with fasta id {fasta_id} does not match any reference for " + f"organism: {config.organism} per `nextclade " + f"{method}`. Double check you are submitting to the correct organism." + ) + if config.alignment_requirement == AlignmentRequirement.ALL: + segment_assignment.alert.errors.append(annotation) + else: + segment_assignment.alert.warnings.append(annotation) + continue + + best_hit = best_hits[best_hits["seqName"] == seq_id] + + not_found = True + for segment in config.nucleotideSequences: + if ( + config.segment_classification_method == SegmentClassificationMethod.ALIGN + and best_hit["segment"].iloc[0] == segment.name + ) or ( + config.segment_classification_method == SegmentClassificationMethod.MINIMIZER + and best_hit["dataset"].iloc[0] in accepted_sort_matches_or_default(segment) + ): + not_found = False + sort_results_map.setdefault(segment.name, []).append(fasta_id) + break + + if not_found: + has_missing_segments = True + annotation = sequence_annotation( + f"Sequence {fasta_id} best matches {best_hit['dataset'].iloc[0]}, " + "which is currently not an accepted option for organism: " + f"{config.organism}. It is therefore not possible to release. " + "Contact the administrator if you think this message is an error." + ) + if config.alignment_requirement == AlignmentRequirement.ALL: + segment_assignment.alert.errors.append(annotation) + else: + segment_assignment.alert.warnings.append(annotation) + + for segment_name, ids in sort_results_map.items(): + if len(ids) > 1: + has_duplicate_segments = True + segment_assignment.alert.errors.append( + sequence_annotation( + f"Multiple sequences (with fasta ids: {', '.join(ids)}) align " + f"to {segment_name} - only one entry is allowed." + ) + ) + continue + + segment_assignment.sequenceNameToFastaId[segment_name] = ids[0] + segment_assignment.unalignedNucleotideSequences[segment_name] = ( + entry.data.unalignedNucleotideSequences[ids[0]] + ) + + if ( + len(segment_assignment.unalignedNucleotideSequences) == 0 + and not has_duplicate_segments + and (not has_missing_segments or config.alignment_requirement == AlignmentRequirement.ANY) + ): + segment_assignment.alert.errors.append( + sequence_annotation( + "No sequence data could be classified - " + "check you are submitting to the correct organism.", + ) + ) + + return segment_assignment def assign_segment_with_nextclade_align( @@ -297,25 +385,14 @@ def assign_segment_with_nextclade_align( ) -> SegmentAssignmentBatch: """ Run nextclade align + - assert sequence aligns to one of the segments in config.nucleotideSequences """ - unaligned_nucleotide_sequences: dict[ - AccessionVersion, dict[SegmentName, NucleotideSequence | None] - ] = defaultdict(dict) - sequenceNameToFastaId: dict[AccessionVersion, dict[SegmentName, str]] = defaultdict(dict) - alerts: Alerts = Alerts() - - has_missing_segments: dict[AccessionVersion, bool] = defaultdict(bool) - has_duplicate_segments: dict[AccessionVersion, bool] = defaultdict(bool) - - align_results_map: dict[AccessionVersion, dict[SegmentName, list[str]]] = defaultdict(dict) - input_unaligned_sequences: dict[ - AccessionVersion, dict[SegmentName, NucleotideSequence | None] - ] = defaultdict(dict) + batch = SegmentAssignmentBatch() all_dfs = [] with TemporaryDirectory(delete=not config.keep_tmp_dir) as result_dir: input_file = result_dir + "/input.fasta" - input_unaligned_sequences, id_map = write_nextclade_input_fasta(unprocessed, input_file) + id_map = write_nextclade_input_fasta(unprocessed, input_file) for sequence_and_dataset in config.nucleotideSequences: segment = sequence_and_dataset.name @@ -341,76 +418,26 @@ def assign_segment_with_nextclade_align( all_dfs.append(df) df_combined = pd.concat(all_dfs, ignore_index=True) - hits = ( - df_combined.dropna(subset=["alignmentScore"]) - .sort_values(by=["seqName", "alignmentScore"], ascending=[True, False]) - .drop_duplicates(subset="seqName", keep="first") + hits = df_combined.dropna(subset=["alignmentScore"]).sort_values( + by=["seqName", "alignmentScore"], ascending=[True, False] ) - - seq_names = set(df_combined["seqName"].tolist()) - seq_names_with_hits = set(hits["seqName"].tolist()) - for seq_name in seq_names - seq_names_with_hits: - (accession_version, fasta_id) = id_map[seq_name] - has_missing_segments[accession_version] = True - annotation = sequence_annotation( - f"Sequence with fasta header {fasta_id} does not align to any segment for" - f" organism: {config.organism} per `nextclade align`. " - f"Double check you are submitting to the correct organism." - ) - if config.alignment_requirement == AlignmentRequirement.ALL: - alerts.errors[accession_version].append(annotation) - else: - alerts.warnings[accession_version].append(annotation) - best_hits = hits.groupby("seqName", as_index=False).first() - logger.debug(f"Found hits: {best_hits['seqName'].tolist()}") - - for _, row in best_hits.iterrows(): - (accession_version, fasta_id) = id_map[row["seqName"]] - for seg in config.nucleotideSequences: - if row["segment"] == seg.name: - align_results_map[accession_version].setdefault(seg.name, []).append(fasta_id) - break - continue - - for accession_version, segment_map in align_results_map.items(): - for segment_name, headers in segment_map.items(): - if len(headers) > 1: - has_duplicate_segments[accession_version] = True - alerts.errors[accession_version].append( - sequence_annotation( - f"Multiple sequences (with fasta headers: {', '.join(headers)}) align to " - f"{segment_name} - only one entry is allowed." - ) - ) - continue - sequenceNameToFastaId[accession_version][segment_name] = headers[0] - unaligned_nucleotide_sequences[accession_version][segment_name] = ( - input_unaligned_sequences[accession_version][headers[0]] - ) for entry in unprocessed: + segment_assignment = assign_segment( + entry, + id_map, + best_hits, + config, + ) accession_version = entry.accessionVersion - if ( - len(unaligned_nucleotide_sequences[accession_version]) == 0 - and not has_duplicate_segments.get(accession_version) - and ( - not has_missing_segments.get(accession_version) - or config.alignment_requirement == AlignmentRequirement.ANY - ) - ): - alerts.errors[accession_version].append( - sequence_annotation( - "No sequence data could be classified - " - "check you are submitting to the correct organism.", - ) - ) + batch.sequenceNameToFastaId[accession_version] = segment_assignment.sequenceNameToFastaId + batch.unalignedNucleotideSequences[accession_version] = ( + segment_assignment.unalignedNucleotideSequences + ) + batch.alerts[accession_version] = segment_assignment.alert - return SegmentAssignmentBatch( - unalignedNucleotideSequences=unaligned_nucleotide_sequences, - sequenceNameToFastaId=sequenceNameToFastaId, - alerts=alerts, - ) + return batch def assign_segment_with_nextclade_sort( @@ -421,19 +448,11 @@ def assign_segment_with_nextclade_sort( - assert highest score is in sequence_and_dataset.accepted_sort_matches (default is nextclade_dataset_name) """ - unaligned_nucleotide_sequences: dict[ - AccessionVersion, dict[SegmentName, NucleotideSequence | None] - ] = defaultdict(dict) - sequenceNameToFastaId: dict[AccessionVersion, dict[SegmentName, str]] = defaultdict(dict) - alerts: Alerts = Alerts() - - has_missing_segments: dict[AccessionVersion, bool] = defaultdict(bool) - has_duplicate_segments: dict[AccessionVersion, bool] = defaultdict(bool) - sort_results_map: dict[AccessionVersion, dict[SegmentName, list[str]]] = defaultdict(dict) + batch = SegmentAssignmentBatch() with TemporaryDirectory(delete=not config.keep_tmp_dir) as result_dir: input_file = result_dir + "/input.fasta" - input_unaligned_sequences, id_map = write_nextclade_input_fasta(unprocessed, input_file) + id_map = write_nextclade_input_fasta(unprocessed, input_file) df = run_sort( result_file=result_dir + "/sort_output.tsv", @@ -442,161 +461,74 @@ def assign_segment_with_nextclade_sort( ) hits = df.dropna(subset=["score"]).sort_values("score", ascending=False) - - seq_names = set(df["seqName"].tolist()) - seq_names_with_hits = set(df.dropna(subset=["score"])["seqName"].tolist()) - for seq_name in seq_names - seq_names_with_hits: - (accession_version, fasta_id) = id_map[seq_name] - has_missing_segments[accession_version] = True - annotation = sequence_annotation( - f"Sequence with fasta header {fasta_id} does not appear to match any reference for" - f" organism: {config.organism} per `nextclade sort`. " - f"Double check you are submitting to the correct organism." - ) - if config.alignment_requirement == AlignmentRequirement.ALL: - alerts.errors[accession_version].append(annotation) - else: - alerts.warnings[accession_version].append(annotation) - best_hits = hits.groupby("seqName", as_index=False).first() - logger.debug(f"Found hits: {best_hits['seqName'].tolist()}") - - for _, row in best_hits.iterrows(): - (accession_version, fasta_id) = id_map[row["seqName"]] - not_found = True - for segment in config.nucleotideSequences: - # TODO: need to check that accepted_sort_matches does not overlap across segments - if row["dataset"] in accepted_sort_matches_or_default(segment): - not_found = False - sort_results_map[accession_version].setdefault(segment.name, []).append(fasta_id) - break - if not not_found: - continue - has_missing_segments[accession_version] = True - annotation = sequence_annotation( - f"Sequence {fasta_id} best matches {row['dataset']}, " - "which is currently not an accepted option for organism: " - f"{config.organism}. It is therefore not possible to release. " - "Contact the administrator if you think this message is an error." - ) - if config.alignment_requirement == AlignmentRequirement.ALL: - alerts.errors[accession_version].append(annotation) - else: - alerts.warnings[accession_version].append(annotation) - - for accession_version, segment_map in sort_results_map.items(): - for segment_name, headers in segment_map.items(): - if len(headers) > 1: - has_duplicate_segments[accession_version] = True - alerts.errors.setdefault(accession_version, []).append( - sequence_annotation( - f"Multiple sequences (with fasta headers: {', '.join(headers)}) align " - f"to {segment_name} - only one entry is allowed." - ) - ) - continue - sequenceNameToFastaId[accession_version][segment_name] = headers[0] - unaligned_nucleotide_sequences[accession_version][segment_name] = ( - input_unaligned_sequences[accession_version][headers[0]] - ) for entry in unprocessed: + segment_assignment = assign_segment( + entry, + id_map, + best_hits, + config, + ) accession_version = entry.accessionVersion - if ( - len(unaligned_nucleotide_sequences[accession_version]) == 0 - and not has_duplicate_segments.get(accession_version) - and ( - not has_missing_segments.get(accession_version) - or config.alignment_requirement == AlignmentRequirement.ANY - ) - ): - alerts.errors.setdefault(accession_version, []).append( - sequence_annotation( - "No sequence data could be classified - " - "check you are submitting to the correct organism.", - ) - ) - - return SegmentAssignmentBatch( - unalignedNucleotideSequences=unaligned_nucleotide_sequences, - sequenceNameToFastaId=sequenceNameToFastaId, - alerts=alerts, - ) + batch.sequenceNameToFastaId[accession_version] = segment_assignment.sequenceNameToFastaId + batch.unalignedNucleotideSequences[accession_version] = ( + segment_assignment.unalignedNucleotideSequences + ) + batch.alerts[accession_version] = segment_assignment.alert + return batch def assign_single_segment( input_unaligned_sequences: dict[str, NucleotideSequence | None], config: Config, ) -> SegmentAssignment: - errors: list[ProcessingAnnotation] = [] - warnings: list[ProcessingAnnotation] = [] - unaligned_nucleotide_sequences: dict[SegmentName, NucleotideSequence | None] = {} - sequenceNameToFastaId: dict[SegmentName, str] = {} if len(input_unaligned_sequences) > 1: - errors.append( - sequence_annotation( - f"Multiple sequences: {list(input_unaligned_sequences.keys())} found in the" - f" input data, but organism: {config.organism} is single-segmented. " - "Please check that your metadata and sequences are annotated correctly." - "Each metadata entry should have a single corresponding fasta sequence " - "entry with the same submissionId." - ) + return SegmentAssignment( + alert=Alert( + errors=[ + sequence_annotation( + f"Multiple sequences: {list(input_unaligned_sequences.keys())} found in the" + f" input data, but organism: {config.organism} is single-segmented. " + "Please check that your metadata and sequences are annotated correctly." + "Each metadata entry should have a single corresponding fasta sequence " + "entry with the same submissionId." + ) + ], + ), ) - else: - fasta_id, value = next(iter(input_unaligned_sequences.items())) - sequenceNameToFastaId["main"] = fasta_id - unaligned_nucleotide_sequences["main"] = value return SegmentAssignment( - unalignedNucleotideSequences=unaligned_nucleotide_sequences, - sequenceNameToFastaId=sequenceNameToFastaId, - errors=errors, - warnings=warnings, + unalignedNucleotideSequences={"main": next(iter(input_unaligned_sequences.values()))}, + sequenceNameToFastaId={"main": next(iter(input_unaligned_sequences.keys()))}, ) def assign_all_single_segments( unprocessed: Sequence[UnprocessedEntry], config: Config ) -> SegmentAssignmentBatch: - unaligned_nucleotide_sequences: dict[ - AccessionVersion, dict[SegmentName, NucleotideSequence | None] - ] = defaultdict(dict) - segment_to_header: dict[AccessionVersion, dict[SegmentName, str]] = defaultdict(dict) - alerts: Alerts = Alerts() + batch = SegmentAssignmentBatch() for entry in unprocessed: accession_version = entry.accessionVersion segment_assignment = assign_single_segment( entry.data.unalignedNucleotideSequences, config=config, ) - segment_to_header[accession_version] = segment_assignment.sequenceNameToFastaId - unaligned_nucleotide_sequences[accession_version] = ( + batch.sequenceNameToFastaId[accession_version] = segment_assignment.sequenceNameToFastaId + batch.unalignedNucleotideSequences[accession_version] = ( segment_assignment.unalignedNucleotideSequences ) - alerts.errors[accession_version] = segment_assignment.errors - alerts.warnings[accession_version] = segment_assignment.warnings - return SegmentAssignmentBatch( - unalignedNucleotideSequences=unaligned_nucleotide_sequences, - sequenceNameToFastaId=segment_to_header, - alerts=alerts, - ) + batch.alerts[accession_version] = segment_assignment.alert + return batch def assign_segment_using_header( input_unaligned_sequences: dict[str, NucleotideSequence | None], config: Config, ) -> SegmentAssignment: - errors: list[ProcessingAnnotation] = [] - warnings: list[ProcessingAnnotation] = [] - unaligned_nucleotide_sequences: dict[SegmentName, NucleotideSequence | None] = {} - sequenceNameToFastaId: dict[SegmentName, str] = {} + segment_assignment = SegmentAssignment() duplicate_segments = set() if not config.nucleotideSequences: - return SegmentAssignment( - unalignedNucleotideSequences={}, - sequenceNameToFastaId={}, - errors=errors, - warnings=warnings, - ) + return segment_assignment if not config.multi_segment: return assign_single_segment(input_unaligned_sequences, config) for sequence_and_dataset in config.nucleotideSequences: @@ -611,7 +543,7 @@ def assign_segment_using_header( ] if len(unaligned_segment) > 1: duplicate_segments.update(unaligned_segment) - errors.append( + segment_assignment.alert.errors.append( sequence_annotation( f"Found multiple sequences with the same segment name: {segment}. " "Each metadata entry can have multiple corresponding fasta sequence " @@ -619,17 +551,17 @@ def assign_segment_using_header( ) ) elif len(unaligned_segment) == 1: - sequenceNameToFastaId[segment] = unaligned_segment[0] - unaligned_nucleotide_sequences[segment] = input_unaligned_sequences[ + segment_assignment.sequenceNameToFastaId[segment] = unaligned_segment[0] + segment_assignment.unalignedNucleotideSequences[segment] = input_unaligned_sequences[ unaligned_segment[0] ] remaining_segments = ( set(input_unaligned_sequences.keys()) - - set(sequenceNameToFastaId.values()) + - set(segment_assignment.sequenceNameToFastaId.values()) - duplicate_segments ) if len(remaining_segments) > 0: - errors.append( + segment_assignment.alert.errors.append( sequence_annotation( f"Found sequences in the input data with segments that are not in the config: " f"{', '.join(remaining_segments)}. " @@ -638,18 +570,13 @@ def assign_segment_using_header( f"{', '.join([seq.name for seq in config.nucleotideSequences])}." ) ) - if len(unaligned_nucleotide_sequences) == 0 and not duplicate_segments: - errors.append( + if len(segment_assignment.unalignedNucleotideSequences) == 0 and not duplicate_segments: + segment_assignment.alert.errors.append( sequence_annotation( - "No sequence data found - ", + "No sequence data found - check segments are annotated correctly.", ) ) - return SegmentAssignment( - unalignedNucleotideSequences=unaligned_nucleotide_sequences, - sequenceNameToFastaId=sequenceNameToFastaId, - errors=errors, - warnings=warnings, - ) + return segment_assignment def load_aligned_nuc_sequences( @@ -838,8 +765,8 @@ def enrich_with_nextclade( # noqa: PLR0914 alignedAminoAcidSequences=aligned_aminoacid_sequences[id], aminoAcidInsertions=amino_acid_insertions[id], sequenceNameToFastaId=segment_assignment_map[id], - errors=alerts.errors[id], - warnings=alerts.warnings[id], + errors=alerts[id].errors, + warnings=alerts[id].warnings, ) for id in input_metadata } diff --git a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py index 5563135bae..2065ab1a05 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py @@ -390,7 +390,9 @@ def alignment_errors_warnings( ) errors.append( ProcessingAnnotation.from_single( - "alignment", AnnotationSourceType.NUCLEOTIDE_SEQUENCE, message=message + ProcessingAnnotationAlignment, + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + message=message, ) ) return (errors, warnings) @@ -509,7 +511,7 @@ def process_single_unaligned( accession_version=accession_version, unprocessed=unprocessed, output_metadata=output_metadata, - errors=list(set(iupac_errors + metadata_errors + segment_assignment.errors)), + errors=list(set(iupac_errors + metadata_errors + segment_assignment.alert.errors)), warnings=list(set(metadata_warnings)), sequenceNameToFastaId=segment_assignment.sequenceNameToFastaId, ) diff --git a/preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/minimizer/minimizer.json b/preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/minimizer/minimizer.json new file mode 100644 index 0000000000..81b74b890b --- /dev/null +++ b/preprocessing/nextclade/tests/ebola-dataset/ebola-sudan/minimizer/minimizer.json @@ -0,0 +1 @@ +{"schemaVersion": "3.0.0", "version": "1", "params": {"k": 17, "cutoff": 2147483648}, "minimizers": {"69543": [0, 1], "71846": [0, 1], "170137": [0], "390225": [0], "695790": [0], "891801": [0], "1021124": [0], "1236686": [0], "1315823": [0], "1361598": [0], "1489080": [0], "1638488": [0], "1712786": [0], "1744960": [0], "2205906": [0], "2207683": [0], "2232227": [0], "2256261": [0], "2314094": [0], "2657358": [0], "2860514": [0], "3019789": [0], "3332777": [0, 1], "3374274": [0], "3381333": [0], "3519654": [0], "3618250": [0], "4380772": [0], "4790460": [0], "5017706": [0], "5817014": [0], "5842603": [0], "5856566": [0, 1], "5965758": [0], "6152586": [0], "6230241": [0], "6237563": [0], "6657098": [0], "6834193": [0], "6997452": [0], "7199056": [0], "7264803": [0], "7588308": [0], "7657028": [0], "7956217": [0], "8076853": [0], "8177912": [0], "8239893": [0], "8507894": [0], "8683684": [0], "8694590": [0], "8913021": [0], "9103477": [0], "9274733": [0], "9355036": [0], "9580612": [0], "9682087": [0], "9706355": [0], "9712770": [0], "9884429": [0], "9890357": [0], "9928977": [0], "10045824": [0], "10224249": [0], "10237242": [0], "10244336": [0], "10274412": [0], "10397189": [0], "10892876": [0], "10919435": [0], "10964485": [0, 1], "10985532": [0], "11706086": [0], "11946366": [0], "12487294": [0], "12597367": [0], "12687721": [0], "13218851": [0], "13353260": [0], "13497653": [0], "13752789": [0], "14231513": [0], "14287530": [0], "14412255": [0], "14475675": [0], "14761505": [0], "15079688": [0], "15108615": [0], "15442741": [0], "15506579": [0], "15619082": [0], "15670607": [0], "15705850": [0], "15773812": [0], "16097971": [0], "16102956": [0], "16362216": [0], "16662114": [0], "17416671": [0], "17607948": [0], "17668446": [0], "17691960": [0], "17904642": [0], "17950997": [0], "17972344": [0], "18010866": [0], "18044147": [0], "18050114": [0], "18057238": [0], "18220116": [0], "18287385": [0], "18323168": [0], "18535032": [0], "18757419": [0], "18768739": [0], "18792955": [0], "18945047": [0], "19129826": [0, 1], "19301390": [0], "19337400": [0], "19508452": [0], "19761131": [0, 1], "19813761": [0], "20045323": [0], "20269901": [0], "20547441": [0], "20548548": [0], "20586755": [0], "20739471": [0], "21526564": [0], "21573297": [0], "21894951": [0], "22006798": [0], "22029184": [0], "22173177": [0], "22183178": [0], "22307098": [0], "22652255": [0], "22701650": [0, 1], "22748139": [0], "23035451": [0], "23159688": [0], "23185800": [0], "23613382": [0], "23898180": [0], "23963613": [0], "24112430": [0], "24251239": [0], "24258613": [0], "24364498": [0], "24415031": [0], "24459412": [0], "24645190": [0], "24707454": [0], "25353425": [0], "25411810": [0], "25464423": [0], "25555172": [0], "25854453": [0], "25865656": [0], "25966565": [0], "26893652": [0], "27064641": [0], "27085654": [0], "27753387": [0, 1], "27900659": [0], "27930358": [0], "28094088": [0], "28142437": [0], "28616334": [0], "29237303": [0], "29297884": [0], "29557555": [0], "29836036": [0], "29844752": [0], "30182111": [0], "30375354": [0], "30458268": [0], "30464050": [0], "31180675": [0], "31310114": [0], "31478204": [0], "31990850": [0], "32480029": [0], "32534490": [0], "32676233": [0, 1], "33262933": [0], "33364701": [0], "33723057": [0], "33814654": [0], "33877493": [0], "33947092": [0], "33989823": [0], "34254946": [0], "34292914": [0], "34460235": [0, 1], "34479366": [0], "34529743": [0], "35102265": [0, 1], "35174403": [0], "35305718": [0], "35333920": [0], "35347717": [0], "35489432": [0, 1], "35544693": [0], "35946090": [0], "35990473": [0], "36240733": [0], "36307880": [0], "36611787": [0], "36958878": [0], "37176964": [0], "37181559": [0], "37341339": [0], "38184768": [0], "38436357": [0], "39235001": [0], "39632772": [0], "39790525": [0], "39867039": [0], "40211608": [0, 1], "40483052": [0, 1], "40508648": [0], "40587990": [0], "40676748": [0], "40784127": [0], "41307635": [0], "41505281": [0], "41884625": [0], "42253443": [0], "42946366": [0], "43085590": [0], "43116533": [0], "43133119": [0], "43939480": [0], "44433940": [0], "44488128": [0], "44972911": [0], "45278921": [0], "45914670": [0], "46383787": [0], "47261906": [0], "47274968": [0], "47439279": [0], "47844668": [0], "48035742": [0], "48281152": [0], "48412392": [0], "48687809": [0], "48696913": [0], "49168102": [0], "49423003": [0], "49435284": [0], "50047573": [0], "50055451": [0], "50084114": [0], "50086011": [0], "50096172": [0], "50148228": [0], "50233467": [0], "50404434": [0], "50428924": [0], "50449420": [0], "50574421": [0], "50608863": [0], "50928235": [0], "51281015": [0], "51323789": [0], "51606627": [0], "52127193": [0], "52867887": [0], "53246391": [0], "53356726": [0], "53497252": [0], "53528362": [0], "53535733": [0], "53601384": [0], "53707058": [0], "53828235": [0], "53871296": [0], "53902110": [0], "53969467": [0], "53975111": [0], "54157406": [0], "54589203": [0], "55015701": [0], "55116443": [0], "55234335": [0], "55290593": [0], "55301817": [0], "55400808": [0], "55428353": [0], "55539450": [0], "55835498": [0], "56261291": [0], "56359438": [0], "56375303": [0], "56402582": [0], "56467036": [0], "56511892": [0], "56719098": [0], "56831568": [0], "57087051": [0], "57547262": [0, 1], "57553100": [0], "57619838": [0, 1], "57806409": [0], "57951909": [0], "57968728": [0], "58076188": [0], "58259379": [0], "58410437": [0], "58846494": [0], "58906697": [0], "59017963": [0], "59216573": [0], "59360905": [0], "59513365": [0], "60407927": [0], "60773760": [0], "61908786": [0], "61969634": [0], "62063872": [0], "62368719": [0], "62448674": [0], "62474454": [0], "62502089": [0], "62649451": [0], "63245138": [0], "63651448": [0], "63732755": [0], "63801062": [0], "63810175": [0], "63850985": [0], "63952575": [0], "64103807": [0], "64259553": [0], "64432570": [0, 1], "64541070": [0, 1], "64656469": [0], "64934740": [0], "64979718": [0], "65610958": [0], "66132123": [0], "66788038": [0], "67080539": [0], "67083513": [0], "67286384": [0], "67470147": [0], "67611961": [0], "67961354": [0], "68278838": [0], "68356024": [0], "68396843": [0], "68488282": [0], "68517268": [0], "69215307": [0], "69460330": [0], "69471371": [0], "70092748": [0], "70333009": [0], "70838054": [0], "70839258": [0], "71211071": [0], "71594703": [0], "71692535": [0], "71888511": [0], "72046330": [0], "72057275": [0], "72277461": [0], "72303593": [0], "72809286": [0], "72901368": [0], "73035753": [0], "73475041": [0], "74081150": [0], "74214451": [0], "74832705": [0], "74951715": [0], "74980869": [0], "75335614": [0], "75543885": [0], "75617822": [0], "76359200": [0], "76782045": [0], "77020029": [0, 1], "77219125": [0], "77389005": [0], "77539036": [0], "77603442": [0], "77816402": [0], "77824732": [0], "78154680": [0], "78708336": [0], "78976570": [0], "79238622": [0], "79392112": [0, 1], "79655443": [0], "79680803": [0], "79729997": [0], "79779982": [0], "80072921": [0], "80292015": [0], "80485899": [0], "80871155": [0], "81592111": [0], "81924676": [0], "81966046": [0], "82089315": [0], "82330633": [0], "82367068": [0, 1], "83476851": [0], "83727619": [0], "84191558": [0, 1], "84741415": [0], "85669795": [0], "86143810": [0], "86381903": [0], "86809978": [0], "86863956": [0], "87029365": [0], "87451653": [0], "87523522": [0], "87897808": [0], "88032659": [0], "88163076": [0], "88293442": [0, 1], "88357073": [0], "88385644": [0], "88608574": [0, 1], "88743670": [0], "88882525": [0], "89096714": [0], "89440112": [0], "89445250": [0, 1], "90138369": [0], "90375986": [0], "91072849": [0], "91544522": [0], "91662923": [0], "91725078": [0], "91861461": [0], "91889346": [0, 1], "91948592": [0], "92548259": [0], "92608258": [0], "92625291": [0], "92708763": [0], "93098758": [0], "93259197": [0], "93502132": [0, 1], "93814560": [0], "93975590": [0], "94180578": [0], "94383318": [0], "94508326": [0], "94535326": [0], "94918204": [0], "95004933": [0], "95090617": [0], "95629285": [0], "95658449": [0], "95661961": [0], "95773734": [0], "95841472": [0], "95880833": [0], "95921832": [0], "95984643": [0], "96047303": [0], "96047458": [0], "96116499": [0], "96506738": [0], "96810400": [0], "97056673": [0], "97581329": [0], "97661046": [0], "97689711": [0], "98121788": [0], "98206218": [0], "98609481": [0], "99062333": [0], "99304148": [0], "99505139": [0], "99730286": [0], "99795431": [0], "100349939": [0], "100370148": [0, 1], "100533946": [0], "100548484": [0], "100669116": [0], "100885590": [0, 1], "101046463": [0, 1], "101134445": [0], "101177795": [0], "101433491": [0], "102018900": [0], "102035626": [0], "102510372": [0], "102599448": [0], "103109886": [0], "103302847": [0], "103315015": [0], "103795110": [0], "103997067": [0], "104128829": [0], "104463608": [0], "104744177": [0, 1], "104832523": [0], "104982140": [0], "105008139": [0], "105229283": [0], "105610419": [0], "105653665": [0], "105851049": [0], "105865211": [0], "106183930": [0], "106488567": [0], "106597809": [0], "106841242": [0], "107116489": [0, 1], "107160485": [0], "107177143": [0], "108046155": [0], "108300895": [0], "108575709": [0], "108838925": [0], "108971462": [0], "109074345": [0], "109404405": [0], "109456426": [0], "109597253": [0], "109638106": [0], "110027175": [0], "110035827": [0], "110058939": [0], "110207800": [0], "110316206": [0], "110340085": [0], "110589546": [0], "111118495": [0], "111522738": [0], "111616208": [0], "112226235": [0], "112631249": [0], "112631253": [0], "112853151": [0], "112950753": [0], "113204767": [0], "113208379": [0], "113278875": [0], "113381009": [0], "113408217": [0], "113712658": [0, 1], "113873240": [0], "113921140": [0], "114285740": [0], "114798144": [0], "115008572": [0], "115024820": [0], "115534789": [0], "115654667": [0], "116156432": [0], "116230993": [0], "116376102": [0], "117507070": [0], "117521197": [0, 1], "117630085": [0], "117745821": [0], "118381504": [0], "118883569": [0], "119250666": [0], "119271174": [0], "119320795": [0], "120164431": [0], "120354822": [0], "120373041": [0], "120555881": [0], "120769550": [0], "121380561": [0], "121686558": [0], "121687906": [0], "122180050": [0], "123254987": [0], "123386420": [0], "123392938": [0], "124319529": [0], "124524725": [0], "124593151": [0], "124734788": [0], "125525720": [0], "125925654": [0], "126265014": [0], "126308982": [0], "126457880": [0], "126498179": [0], "126697486": [0], "126873114": [0], "127240338": [0], "127240647": [0], "127320245": [0], "127407239": [0], "127599142": [0], "127677548": [0], "127998637": [0], "128093092": [0], "128838055": [0], "129015209": [0], "129548298": [0], "129651254": [0], "129677771": [0], "129831409": [0], "130574023": [0], "131076951": [0], "131083429": [0], "131471224": [0], "131484759": [0], "131528566": [0], "131942487": [0], "131980592": [0], "132061195": [0], "132397025": [0], "132545386": [0], "132550228": [0], "133254230": [0], "133420041": [0], "134199756": [0], "134232972": [0], "134559009": [0], "134820215": [0], "135059528": [0], "135061757": [0], "135102132": [0], "135221418": [0], "135520416": [0], "135683155": [0], "135892683": [0], "136105253": [0], "136193987": [0], "136309032": [0], "136641639": [0], "136654782": [0], "136685143": [0], "137053385": [0], "137191767": [0], "137212189": [0, 1], "137598969": [0], "137811805": [0], "138033143": [0], "138349455": [0], "138613330": [0], "138880282": [0], "139335971": [0], "139369208": [0], "139593067": [0], "139728151": [0], "139756203": [0], "140010452": [0], "140196505": [0], "140307113": [0], "141081855": [0], "141458159": [0], "141541801": [0], "141988965": [0], "141995694": [0, 1], "142308538": [0], "142845339": [0], "142864137": [0], "143085770": [0], "143128189": [0], "143193237": [0], "143453019": [0], "143541595": [0], "143688988": [0], "143826658": [0], "143835654": [0], "143839194": [0], "144258997": [0], "144898971": [0], "145229905": [0], "145324269": [0], "145335475": [0], "145483865": [0], "145629925": [0], "145750995": [0], "145942740": [0, 1], "146247439": [0], "146339141": [0], "146634714": [0], "146819826": [0], "146898050": [0], "147119697": [0], "147508565": [0], "147917614": [0], "147965352": [0], "148247440": [0], "148834296": [0], "149122423": [0, 1], "149189904": [0], "149360275": [0], "149383474": [0], "149609885": [0], "149654056": [0], "150140148": [0], "150191321": [0], "150425411": [0, 1], "150637385": [0], "150723583": [0], "150884197": [0], "150892711": [0], "150983450": [0], "151288764": [0], "151522450": [0], "151811731": [0], "152336660": [0], "153226049": [0], "153655716": [0], "154060701": [0], "154188563": [0], "155070812": [0], "155196881": [0], "155663213": [0], "155676508": [0], "155848723": [0], "155944529": [0], "156463006": [0], "156823116": [0], "157127493": [0], "157129988": [0], "157307253": [0], "157321948": [0], "157681116": [0, 1], "157861943": [0], "159506800": [0], "159556697": [0], "160109008": [0], "160130767": [0], "160325404": [0], "160373788": [0], "160541634": [0], "160569021": [0], "160717196": [0], "160927683": [0], "161050728": [0], "161241974": [0], "162161964": [0], "162540155": [0], "162611348": [0], "163226212": [0], "163605946": [0], "163618296": [0], "164011773": [0, 1], "164368195": [0], "164654107": [0], "164753321": [0], "164875397": [0], "165078621": [0], "165304681": [0], "165690143": [0, 1], "165789986": [0], "166010412": [0], "166062369": [0], "166091309": [0], "166158641": [0], "166174607": [0], "166278807": [0], "166447704": [0], "166674515": [0], "167593823": [0], "168345856": [0], "168875193": [0], "168944283": [0], "169019612": [0], "169539025": [0], "169721645": [0], "169842354": [0], "170048079": [0], "170153486": [0], "170293590": [0], "170309462": [0], "170442381": [0], "170767675": [0, 1], "170806269": [0], "171732287": [0], "172044093": [0], "172082513": [0], "172487309": [0], "173243223": [0], "173576921": [0], "173819840": [0], "173877926": [0], "173891774": [0], "174101712": [0], "174136721": [0], "174303291": [0], "174309748": [0], "174891977": [0], "174897990": [0], "174937499": [0], "175134599": [0], "175166118": [0], "175553542": [0, 1], "175627607": [0], "175733512": [0], "176276831": [0], "176844787": [0], "177278307": [0], "177885229": [0], "178254118": [0], "178664776": [0], "179076273": [0], "179335359": [0], "179624277": [0], "179645107": [0], "180129928": [0], "180226669": [0], "180253465": [0], "180536331": [0], "180552645": [0], "180706270": [0], "181962083": [0], "182024256": [0, 1], "182186150": [0], "182231949": [0], "182240277": [0], "182494695": [0], "182684611": [0], "183302253": [0, 1], "183850225": [0], "183858744": [0], "183979076": [0], "184324582": [0], "184353725": [0], "184418769": [0], "184423415": [0], "184495374": [0], "185184341": [0], "186456054": [0], "186845740": [0], "187103484": [0], "187236137": [0], "187238383": [0], "187387879": [0, 1], "187520614": [0, 1], "187520695": [0], "187663381": [0], "188029221": [0], "188111702": [0], "188380727": [0], "188435550": [0], "188484815": [0], "188487551": [0], "188530035": [0], "188627863": [0], "188703128": [0], "188733542": [0], "188879517": [0], "188937354": [0], "189490976": [0], "189569329": [0], "189720889": [0], "189776915": [0], "190314941": [0, 1], "190714747": [0], "190765889": [0], "191418137": [0], "191711870": [0], "191825896": [0], "191849389": [0], "191935754": [0], "192072462": [0], "192154944": [0], "192157033": [0], "192162825": [0], "192355943": [0], "192565040": [0], "192981315": [0], "193098259": [0], "193134063": [0], "193160769": [0], "193281754": [0], "193866995": [0], "193882752": [0], "193889937": [0], "194113057": [0], "194155133": [0], "194402200": [0], "194540163": [0], "194905876": [0], "195153361": [0], "195220823": [0], "195437542": [0], "195475810": [0], "195640721": [0], "195965153": [0], "196157891": [0], "196185012": [0], "196193407": [0], "196880716": [0], "196918216": [0], "196923654": [0], "196931352": [0], "197040396": [0], "197144639": [0], "197404796": [0], "197504890": [0], "198163342": [0], "198583663": [0], "198832577": [0], "199092559": [0], "199187949": [0], "199371780": [0], "199720838": [0], "199732294": [0], "199980808": [0], "200076790": [0], "200217000": [0], "200244877": [0], "200288442": [0], "200481027": [0], "200820155": [0], "201045422": [0], "201210045": [0], "202164581": [0], "202331432": [0], "202396622": [0], "202605705": [0], "203234249": [0], "203314388": [0], "203581458": [0], "203790614": [0], "203921386": [0], "204226888": [0], "204314158": [0], "204420730": [0], "204452669": [0], "204498228": [0], "205194907": [0], "205269412": [0], "205362561": [0], "206320241": [0], "207366784": [0], "207451307": [0], "207502208": [0], "207612468": [0], "207689809": [0], "208397277": [0], "208631139": [0], "208900781": [0], "209420971": [0], "209649188": [0], "209736590": [0], "210035596": [0], "210038522": [0], "210217696": [0], "210420339": [0], "210660656": [0], "211284092": [0], "211298879": [0], "211686805": [0], "212208874": [0], "212437737": [0], "212741246": [0], "212764298": [0], "212888265": [0], "212946296": [0], "212981310": [0], "213014756": [0], "213041103": [0], "213152644": [0], "213159844": [0], "214526307": [0, 1], "214669699": [0], "215468875": [0], "215875979": [0], "215940619": [0], "216124109": [0], "216162245": [0], "216376085": [0], "217020514": [0], "217497013": [0], "217596884": [0], "217609760": [0], "217673320": [0], "217853086": [0], "217986529": [0], "218081989": [0], "218274326": [0], "219232505": [0], "219257491": [0], "219495016": [0], "219615655": [0], "219755092": [0], "219906968": [0], "219977921": [0], "220066254": [0], "220079933": [0], "220384660": [0], "220821630": [0], "221470164": [0], "221480000": [0], "221517127": [0], "221691034": [0], "221740659": [0, 1], "222393426": [0], "222864465": [0], "223411830": [0], "223685233": [0], "223780888": [0], "223808164": [0, 1], "224705755": [0], "224807576": [0], "224856075": [0, 1], "225143660": [0], "225311424": [0], "225338308": [0], "225453583": [0], "225649876": [0], "226112611": [0], "226297851": [0], "226387447": [0], "226725799": [0], "227139806": [0], "227199668": [0], "227607606": [0], "227681053": [0], "228309134": [0], "228323721": [0], "228406233": [0], "228685927": [0, 1], "228785170": [0], "228935114": [0], "229052691": [0], "229271971": [0], "229568793": [0], "229739542": [0], "229921501": [0], "230054081": [0], "230093197": [0], "231811352": [0], "231904756": [0], "231975834": [0], "232066603": [0], "232070266": [0], "232221050": [0], "232301673": [0], "232826106": [0], "233038718": [0], "233219313": [0], "233267889": [0], "233781459": [0], "233824620": [0], "234513098": [0], "234916749": [0], "234956373": [0], "235120714": [0], "235391449": [0], "235902808": [0], "236156589": [0], "236215057": [0, 1], "236555806": [0], "236711435": [0], "236991547": [0], "237303559": [0], "237527901": [0], "237724446": [0], "237839598": [0], "238080081": [0], "238293219": [0], "238457947": [0], "238887681": [0], "238913900": [0], "238967025": [0], "238969855": [0], "239085833": [0], "239088971": [0], "239305801": [0], "239458435": [0], "239597874": [0], "239818040": [0], "239870220": [0], "240200037": [0], "240285750": [0], "240523264": [0], "240529529": [0], "240909726": [0], "241067995": [0], "241313001": [0], "241923292": [0], "242154993": [0], "242182807": [0], "242383497": [0], "242744640": [0], "242938111": [0], "243851132": [0], "244092686": [0], "244226618": [0], "244980523": [0, 1], "245446096": [0], "245492599": [0], "245564441": [0], "245678848": [0], "245776254": [0], "245921921": [0], "245989638": [0], "246620254": [0], "246760350": [0], "247296516": [0], "247565746": [0], "247885726": [0], "248091596": [0], "248120843": [0], "249049538": [0], "249553077": [0], "249596575": [0], "249837987": [0], "249841037": [0], "250156439": [0, 1], "250164185": [0], "250914859": [0], "250924063": [0], "250934734": [0], "251026124": [0], "251067944": [0], "252161994": [0], "252289661": [0], "252356441": [0], "252379294": [0], "252822148": [0], "252947754": [0], "253076351": [0], "253277461": [0], "253351345": [0], "254002287": [0], "254536115": [0], "254538885": [0], "254592282": [0], "255252600": [0], "255345993": [0], "255408394": [0], "255503272": [0], "255551085": [0], "255732947": [0], "255816963": [0], "256021381": [0], "256162456": [0, 1], "256292284": [0], "256417096": [0], "256584478": [0], "256649996": [0], "256763176": [0], "256768895": [0], "256774758": [0], "257042821": [0], "257137837": [0], "257165337": [0], "257301968": [0, 1], "257453874": [0], "257554948": [0], "257647121": [0], "257669749": [0], "258249080": [0], "258427558": [0], "258874562": [0], "259428476": [0], "259687609": [0], "260015600": [0], "260245278": [0], "260752225": [0], "260999554": [0], "261375597": [0], "261390048": [0], "262077795": [0], "262247239": [0], "262247361": [0], "262306861": [0], "262562180": [0, 1], "262745488": [0], "263100605": [0], "263245189": [0], "263298049": [0], "263828151": [0], "264613942": [0], "264651246": [0], "264798983": [0], "264950442": [0], "265148984": [0], "265728020": [0], "265728723": [0], "265952126": [0], "266053898": [0], "266241572": [0], "266467153": [0], "266750793": [0], "267821291": [0], "267892650": [0], "268488368": [0], "268711431": [0], "268945661": [0], "269620004": [0], "269680663": [0, 1], "269877544": [0, 1], "270311702": [0], "270340479": [0], "270551024": [0], "271207385": [0], "271435583": [0], "272057442": [0], "272554750": [0], "272984235": [0], "273175159": [0], "273361470": [0], "274092279": [0], "274265028": [0], "274377972": [0], "275403121": [0], "275459085": [0, 1], "275464458": [0], "275703498": [0, 1], "275840476": [0, 1], "276059938": [0], "276090731": [0], "276148559": [0, 1], "276463771": [0], "277078698": [0], "277138829": [0], "277374024": [0], "277456387": [0], "277849847": [0], "277902632": [0], "277990936": [0], "277993739": [0], "278054598": [0], "278127227": [0], "278458093": [0], "278497444": [0], "279048517": [0, 1], "279137674": [0], "279265117": [0], "279304791": [0], "279569321": [0], "279829374": [0], "279900278": [0], "279967540": [0], "280265929": [0], "280320626": [0], "280884520": [0], "281199059": [0], "281421965": [0], "281553650": [0], "281838630": [0], "281867022": [0], "281958309": [0, 1], "282115685": [0], "282146627": [0], "282170511": [0], "282515001": [0], "282543050": [0], "282759091": [0], "282944020": [0], "283346416": [0], "283973955": [0], "284108542": [0], "284249509": [0, 1], "285536944": [0], "285651275": [0], "285761502": [0], "285838245": [0], "285998497": [0], "286115610": [0], "286126641": [0], "286827371": [0], "287100261": [0], "287101334": [0], "287331555": [0], "287369081": [0, 1], "287998653": [0], "288286689": [0], "288646202": [0], "288751724": [0], "288764558": [0], "288781345": [0], "289464761": [0], "289564463": [0], "289831924": [0], "290005260": [0], "290123471": [0], "290300940": [0], "290519967": [0], "290826298": [0], "290864560": [0], "291193801": [0], "291267577": [0], "291434006": [0], "291484158": [0], "291764062": [0], "291783909": [0, 1], "292147030": [0], "292170252": [0], "292505173": [0], "292519298": [0], "292583066": [0], "292651633": [0], "292805095": [0], "292973765": [0], "293582051": [0], "293932462": [0], "293990191": [0], "294415392": [0], "294467128": [0], "295243274": [0], "295598361": [0], "295753288": [0], "295769282": [0], "296135401": [0], "296178031": [0], "296647522": [0], "296712482": [0], "297069354": [0], "297167588": [0], "297223225": [0], "297266278": [0], "297574469": [0], "297832633": [0], "298126434": [0], "298203807": [0], "298845620": [0], "299235649": [0], "299533186": [0], "299773066": [0], "300223089": [0], "300236917": [0], "300453987": [0], "300846896": [0], "301414233": [0, 1], "301521739": [0], "301620089": [0], "301714741": [0], "301860710": [0], "302544856": [0], "302692137": [0], "302854923": [0], "303417319": [0], "304038547": [0], "304347546": [0], "304594319": [0], "304692104": [0], "304886591": [0], "304888363": [0], "305073077": [0], "305587977": [0], "305685841": [0], "305977209": [0], "306017852": [0], "306574743": [0], "306899405": [0], "307152869": [0], "307410795": [0], "307452553": [0], "307575654": [0], "307698304": [0], "307846988": [0], "308145880": [0], "308272905": [0], "308436561": [0], "308654696": [0, 1], "308877867": [0], "309201298": [0], "309229686": [0], "309388219": [0], "309449902": [0], "309700940": [0], "309800942": [0], "310058350": [0], "310071925": [0], "310115163": [0], "310122610": [0], "310944311": [0], "311003762": [0, 1], "311149539": [0], "311325605": [0], "311393594": [0], "311441206": [0], "312099727": [0], "312124250": [0], "312137270": [0], "312414128": [0], "312470508": [0], "312567151": [0, 1], "313056524": [0], "313390054": [0], "313468657": [0], "313550196": [0], "313614110": [0, 1], "313620512": [0, 1], "314335815": [0], "314363657": [0], "314375386": [0], "314474855": [0], "315341354": [0], "315365517": [0], "315441006": [0], "315479415": [0], "315488965": [0], "315597289": [0], "315864500": [0], "316388655": [0], "316700501": [0], "317000842": [0], "317104158": [0], "317207492": [0], "317389483": [0], "317424779": [0, 1], "317451981": [0], "317484694": [0], "317485701": [0], "317821208": [0], "317899639": [0], "318084353": [0], "318157945": [0], "318312068": [0], "318413902": [0], "318690683": [0], "318804621": [0], "319041952": [0], "319095298": [0], "319368329": [0], "319919381": [0], "320154353": [0], "320163505": [0], "320502510": [0], "320789740": [0], "320918619": [0], "321200569": [0], "321413197": [0], "321735416": [0], "321876307": [0], "322069632": [0], "322835064": [0, 1], "322852095": [0, 1], "323168257": [0], "323190024": [0], "323269268": [0], "323338128": [0], "323735982": [0], "323845285": [0], "323889856": [0], "324039663": [0], "325028434": [0], "325350573": [0], "325583995": [0], "325608475": [0], "325625585": [0], "325721667": [0], "325933372": [0], "325961111": [0], "326070656": [0], "326294930": [0], "326423053": [0], "326715676": [0], "327246597": [0], "327477526": [0], "327587091": [0], "327638247": [0], "327831303": [0], "327951146": [0], "328267457": [0], "328631761": [0], "328695654": [0], "329373516": [0], "329568049": [0], "329791286": [0], "329866247": [0], "330345570": [0], "330483612": [0], "330498879": [0], "330806060": [0], "330867357": [0], "331163674": [0], "331629849": [0], "331751807": [0], "331948066": [0], "331989378": [0], "332026975": [0], "332109662": [0], "332197125": [0], "332447621": [0], "332494513": [0], "332575810": [0], "332598599": [0], "333030595": [0], "333175856": [0], "333865347": [0], "333923789": [0], "334186249": [0], "334664245": [0], "334749370": [0], "334776545": [0], "334965583": [0], "335053898": [0], "335160636": [0], "335404231": [0], "335518588": [0], "335916767": [0], "336569514": [0], "336922830": [0], "336975594": [0, 1], "337052318": [0], "337108309": [0], "337297381": [0], "337820423": [0], "337914843": [0], "338223634": [0], "338429343": [0, 1], "338636146": [0], "338700786": [0], "338863896": [0], "338889043": [0], "339042992": [0], "339077713": [0], "339106766": [0], "339212669": [0], "339752732": [0], "339846111": [0], "340253370": [0], "340361724": [0], "340486200": [0], "340513317": [0], "340531638": [0], "340712568": [0], "340715864": [0, 1], "340862221": [0], "340904864": [0, 1], "341487438": [0], "341538822": [0], "341812625": [0], "342354879": [0], "342812452": [0], "342839586": [0], "343763147": [0], "344266653": [0], "344348527": [0], "344426768": [0], "345019564": [0], "345119630": [0, 1], "345290112": [0], "345482856": [0], "345486049": [0], "345807172": [0], "346860479": [0], "347009964": [0], "347550254": [0], "347795268": [0], "348006369": [0], "348078268": [0], "348114879": [0], "348170702": [0, 1], "348308198": [0], "348365486": [0], "348444563": [0], "348489157": [0], "348710961": [0], "348880108": [0], "349094507": [0], "349250456": [0], "349267111": [0], "349380938": [0], "349621373": [0], "349657843": [0], "349734496": [0], "350127999": [0], "350166488": [0], "350651412": [0], "350909348": [0], "350928653": [0], "350992061": [0], "351196122": [0], "351293188": [0], "351318022": [0], "352394355": [0], "352553154": [0], "352938566": [0], "353002372": [0], "353058434": [0], "354051396": [0], "354325572": [0, 1], "354352866": [0], "354717804": [0], "355230055": [0], "355637114": [0], "355676182": [0], "355701494": [0], "355819631": [0], "355957898": [0], "356113051": [0, 1], "356297794": [0], "356748989": [0], "356885738": [0], "356897479": [0], "356970904": [0], "356972027": [0], "357134422": [0], "357222384": [0], "357653306": [0], "358370301": [0], "358385343": [0, 1], "358448355": [0], "358987667": [0], "359301856": [0], "359313960": [0], "359651274": [0], "359725933": [0], "360005665": [0], "360095257": [0], "360096552": [0, 1], "360498079": [0], "360541263": [0], "360711687": [0], "360900508": [0], "361439705": [0], "363064351": [0], "363197472": [0], "363198793": [0], "363208007": [0], "363218485": [0], "363337492": [0], "363428747": [0], "363444733": [0], "363901385": [0], "363955834": [0], "364244101": [0], "364663295": [0], "365073832": [0], "365178494": [0], "365298471": [0], "365412256": [0], "365837802": [0], "366126368": [0], "366171189": [0], "366929551": [0], "367009121": [0], "367101844": [0], "367433440": [0], "367449903": [0], "367612754": [0], "367735129": [0], "367763780": [0], "368259700": [0], "368892291": [0], "369025575": [0], "369207128": [0], "369353139": [0], "369513350": [0], "369623081": [0], "369652425": [0], "370243736": [0], "370318941": [0], "371048578": [0], "372221498": [0], "372575530": [0], "372773615": [0], "372781703": [0], "372801848": [0], "373501617": [0], "373519674": [0], "373622459": [0], "374083000": [0], "374221274": [0], "374603764": [0], "374651354": [0], "375170912": [0], "375437008": [0], "375797696": [0], "376599038": [0], "376611176": [0], "376637070": [0, 1], "376711579": [0], "376917274": [0], "377182053": [0], "377211144": [0], "377682745": [0], "377896308": [0], "378172033": [0], "378471475": [0], "378478081": [0], "378496667": [0], "378682324": [0], "378959331": [0], "379412814": [0], "379451364": [0], "379548254": [0], "379555038": [0], "379577379": [0], "380315429": [0], "380355230": [0], "380648295": [0, 1], "381291555": [0], "381607264": [0], "382167851": [0], "382215146": [0], "382582045": [0], "382768342": [0], "383283663": [0], "383304380": [0], "383446765": [0], "383491154": [0], "383807155": [0], "383925278": [0], "384137170": [0], "384254877": [0], "384555822": [0], "384574370": [0], "384633156": [0], "384945688": [0], "385382973": [0], "385693566": [0], "386067394": [0], "386799405": [0], "386955080": [0], "387413389": [0], "387604100": [0], "387896524": [0], "388721479": [0, 1], "388991661": [0], "389373580": [0], "389804057": [0], "389851760": [0], "390051431": [0], "390196269": [0], "390228038": [0], "390893024": [0], "391163285": [0], "391413980": [0], "391795982": [0], "392007652": [0], "392234088": [0, 1], "392507973": [0], "393248793": [0], "393376178": [0], "394345284": [0], "394359718": [0], "394438449": [0], "394676667": [0], "395098866": [0], "395474608": [0], "395745507": [0], "395777043": [0, 1], "395860684": [0], "395921188": [0], "395951875": [0], "396750981": [0], "396780470": [0], "396980518": [0], "397371069": [0, 1], "397556908": [0], "397719315": [0], "397766794": [0], "398043610": [0], "398105261": [0], "399313569": [0], "399590958": [0], "399729976": [0], "400259049": [0], "400269723": [0], "400324592": [0], "400479446": [0], "400773052": [0], "400895910": [0], "401453675": [0], "401611017": [0], "402002541": [0], "402040304": [0], "402041278": [0], "402804247": [0], "402879783": [0], "402908883": [0], "403224798": [0], "403494393": [0], "403628304": [0], "403812454": [0], "403965765": [0], "403980441": [0], "403992983": [0], "404098196": [0, 1], "404277572": [0], "404584064": [0], "404728657": [0], "404778772": [0, 1], "404801139": [0], "405035131": [0], "405181541": [0], "405244981": [0], "406675262": [0], "406684747": [0], "406823874": [0], "406971103": [0], "407000942": [0], "407101308": [0], "407116828": [0], "407239220": [0], "407427669": [0], "407440667": [0], "407502504": [0, 1], "407525662": [0], "407614240": [0], "407746476": [0], "407957030": [0], "408028328": [0], "408718039": [0], "409109375": [0], "409574927": [0], "410148565": [0], "410433966": [0], "410513936": [0], "410525916": [0], "410571672": [0], "410627894": [0], "410756837": [0], "411221162": [0], "411497591": [0], "411756198": [0], "411928314": [0], "411978136": [0], "412211207": [0], "412301107": [0], "412366130": [0], "412396687": [0, 1], "412444884": [0], "412511990": [0], "412704463": [0], "412772734": [0], "412935571": [0], "413211883": [0], "413248318": [0], "413315338": [0], "413432131": [0], "413879128": [0, 1], "413918571": [0], "413985250": [0], "414074069": [0, 1], "414527314": [0, 1], "414985277": [0], "415552153": [0], "416041576": [0], "416295447": [0], "416374900": [0], "416435180": [0], "416884573": [0], "417050397": [0], "417092091": [0], "417224255": [0], "417430854": [0], "418393985": [0], "418446222": [0], "418647649": [0], "418721075": [0], "418852647": [0], "418908025": [0], "419304960": [0], "419354685": [0], "419584779": [0], "419782680": [0, 1], "419796522": [0], "420015314": [0], "420191219": [0], "421039219": [0], "421190756": [0], "421215245": [0], "421356234": [0], "421364330": [0], "421415615": [0], "421871015": [0], "422016323": [0], "422297415": [0], "422970497": [0], "423034394": [0, 1], "424663156": [0], "424680247": [0, 1], "424734539": [0, 1], "425171468": [0], "425640900": [0], "425708755": [0, 1], "425874228": [0], "426031570": [0], "426109703": [0], "426180928": [0], "426936614": [0], "426941929": [0], "426994449": [0], "427115980": [0], "427307396": [0], "427546391": [0], "428034107": [0], "428142884": [0], "428573349": [0], "428841595": [0], "428938561": [0], "429459411": [0], "430481132": [0, 1], "430491980": [0], "430929142": [0], "431082368": [0], "431404747": [0], "431498087": [0, 1], "431513199": [0], "431542134": [0], "431845496": [0], "432524531": [0], "433019917": [0], "433436814": [0], "433722666": [0], "433843616": [0], "433896588": [0], "433972566": [0], "434129719": [0, 1], "434160554": [0], "434206904": [0], "434431386": [0], "434797431": [0, 1], "435044696": [0], "435286158": [0], "435306832": [0], "435756806": [0], "435875495": [0], "435919066": [0], "436192555": [0], "436334712": [0], "436818171": [0], "437616367": [0], "437676662": [0], "438055027": [0], "438184709": [0], "438185674": [0], "438289172": [0], "438904484": [0], "439190784": [0], "439212967": [0], "439338362": [0], "440557875": [0], "441487566": [0], "441613140": [0], "441830701": [0], "442027998": [0], "442134418": [0], "442735988": [0, 1], "442832912": [0], "443062111": [0], "443074962": [0, 1], "443702612": [0], "443892439": [0], "443963053": [0], "444068067": [0], "444602151": [0], "444815883": [0], "444881429": [0, 1], "444997483": [0], "445090399": [0], "445202076": [0], "445447726": [0], "445602382": [0], "446144293": [0], "446483208": [0], "446556112": [0], "446798566": [0], "446935861": [0], "446966235": [0], "447309709": [0], "447351472": [0], "447410813": [0], "447569451": [0], "448063449": [0], "448074465": [0], "448076148": [0], "449009608": [0], "449084975": [0], "449260967": [0, 1], "449305917": [0], "449370414": [0], "450852331": [0], "451556617": [0], "451573129": [0], "451982056": [0], "452075420": [0], "452092599": [0], "452216079": [0], "452298088": [0], "452368005": [0], "452503240": [0, 1], "452816957": [0], "452846954": [0], "453017552": [0], "453117939": [0], "453131432": [0], "453213111": [0], "453414738": [0, 1], "453429008": [0], "453729597": [0], "454075511": [0], "454543569": [0], "454985085": [0], "455266987": [0], "455356290": [0], "455444277": [0], "455543211": [0], "455687003": [0], "455738966": [0], "455762729": [0], "455925678": [0], "456753772": [0], "456755023": [0], "456989192": [0], "457038433": [0], "457467527": [0], "457497507": [0], "457646334": [0], "457840820": [0], "457877425": [0], "457942861": [0], "458245964": [0], "458339564": [0], "458804210": [0], "459218190": [0], "459935911": [0], "460075409": [0], "460146513": [0], "460223942": [0], "460601563": [0], "460678875": [0], "461003635": [0], "461125375": [0], "461214357": [0], "461330018": [0], "461597927": [0], "461726118": [0], "461868796": [0], "462003109": [0], "462162181": [0], "462573240": [0], "462826762": [0], "463010326": [0], "463276342": [0], "463465182": [0], "463544054": [0], "463636130": [0], "464088390": [0], "464459713": [0], "464579730": [0], "464814466": [0], "465047791": [0], "465165131": [0], "465254226": [0], "465457282": [0, 1], "465535910": [0], "465731294": [0], "465914505": [0], "466051741": [0], "466331612": [0], "466425795": [0], "466762018": [0], "466905523": [0, 1], "467020736": [0, 1], "467317796": [0], "467358798": [0], "467387796": [0], "467717241": [0], "467978003": [0], "468909542": [0], "468928438": [0], "469357284": [0], "469402236": [0], "469610657": [0], "469671730": [0], "469748531": [0], "470407468": [0], "470451737": [0], "470497474": [0], "470557716": [0, 1], "471063251": [0, 1], "471435509": [0], "471687605": [0], "471943665": [0], "472141616": [0], "472915678": [0], "472994805": [0], "473406351": [0], "474183922": [0], "474341235": [0], "474600492": [0], "474662618": [0], "474882239": [0], "476202548": [0], "476604846": [0], "476686255": [0], "476718071": [0], "477047342": [0], "477142533": [0], "477271360": [0], "477385230": [0], "477405830": [0], "477617375": [0], "477642108": [0], "477915367": [0], "477920498": [0], "478072268": [0], "478082589": [0], "478274381": [0], "478373739": [0], "478589417": [0], "478863214": [0], "478909222": [0], "479019192": [0], "479611566": [0], "479792215": [0], "480321730": [0], "480531608": [0], "480834630": [0], "480965751": [0], "481066538": [0], "481329249": [0], "481673243": [0], "481996348": [0], "482089050": [0], "482253467": [0], "482435610": [0], "482897418": [0], "482916242": [0], "482960943": [0], "483085745": [0], "483180179": [0], "483305294": [0], "483468595": [0], "483522083": [0], "483940448": [0], "484084827": [0], "484102532": [0], "484196209": [0], "484321672": [0], "484566478": [0], "484841521": [0], "485024213": [0], "485226940": [0], "485236744": [0], "485259865": [0], "485337979": [0], "485358252": [0], "486091284": [0], "486129756": [0], "486205768": [0], "486810741": [0], "487529240": [0], "488560839": [0], "489001964": [0], "489408945": [0, 1], "489862863": [0], "490216362": [0], "490392129": [0], "491124662": [0], "491556548": [0], "491562148": [0], "491708032": [0], "491878180": [0], "491954212": [0], "492284707": [0], "492896373": [0], "492973033": [0], "493090624": [0], "493338184": [0, 1], "493797492": [0], "493931041": [0], "494207180": [0], "494554710": [0], "495267940": [0], "495364592": [0], "495702296": [0], "495754379": [0], "495934940": [0], "496090324": [0], "496507253": [0], "496876676": [0], "497288631": [0], "497290236": [0], "497466073": [0, 1], "497473812": [0], "497482392": [0], "497771844": [0], "498030623": [0], "498325530": [0], "498460971": [0], "498491555": [0], "498903501": [0], "499341998": [0], "499656695": [0], "499704704": [0], "499875544": [0], "500069730": [0], "500261721": [0], "500296008": [0], "500439701": [0], "500467765": [0], "500532499": [0], "500564960": [0], "500603225": [0], "500721643": [0], "500747788": [0], "500787120": [0], "500975380": [0], "501019109": [0], "501281523": [0], "501304816": [0], "501341015": [0], "501428255": [0], "501618760": [0], "501655719": [0], "501916916": [0], "502146765": [0], "502172165": [0], "502319459": [0], "502630234": [0, 1], "502868300": [0], "503110428": [0], "503736874": [0], "504320834": [0], "504710279": [0], "504723216": [0], "505724785": [0], "506148896": [0], "506220406": [0], "506532946": [0], "507024215": [0, 1], "507026058": [0], "507052217": [0], "507083804": [0], "507290856": [0], "507715925": [0], "508009315": [0], "508009385": [0], "508288960": [0], "508532181": [0], "508787126": [0], "508940825": [0], "509004228": [0], "509006891": [0], "509297101": [0], "509371307": [0], "509503579": [0, 1], "509678263": [0], "509808983": [0, 1], "509911612": [0], "510063276": [0], "510099618": [0], "510117325": [0], "510145013": [0], "510202498": [0], "510457133": [0], "510935902": [0], "511050951": [0], "511208129": [0], "511257924": [0], "511633549": [0, 1], "511671532": [0], "511706672": [0], "511952984": [0], "512611716": [0], "512801513": [0], "513316688": [0], "513490716": [0], "513652868": [0], "513715614": [0], "513788519": [0], "513848459": [0, 1], "514601417": [0], "514908234": [0], "514920325": [0], "514970323": [0, 1], "515700357": [0], "515916599": [0], "515943011": [0], "516272236": [0], "516395046": [0], "516449509": [0], "516491879": [0], "516550357": [0], "516563876": [0], "516637911": [0], "516720209": [0], "516799900": [0], "516905097": [0], "517077422": [0], "517255653": [0, 1], "517947063": [0], "518022559": [0], "518491474": [0], "518639455": [0], "518720825": [0], "518787087": [0], "518798274": [0], "518984104": [0], "518990037": [0], "518995906": [0], "519101064": [0], "519269582": [0, 1], "519614210": [0], "519991823": [0, 1], "520050089": [0], "520096602": [0], "520104654": [0], "520299289": [0], "520668819": [0], "520967742": [0], "520974095": [0], "521057508": [0], "521223581": [0], "521324542": [0], "521409358": [0], "521882769": [0], "522230981": [0], "522539285": [0], "523117340": [0], "523131958": [0], "523164560": [0], "523870547": [0], "523993893": [0], "524241740": [0], "524542289": [0], "524684238": [0], "524741622": [0], "524950095": [0], "524977781": [0], "525376825": [0], "525542968": [0], "525968881": [0], "526520677": [0], "526592081": [0], "526673463": [0], "526718475": [0], "526760274": [0], "526894460": [0], "527177451": [0, 1], "527261880": [0], "527683112": [0], "527807219": [0], "527940748": [0], "528015816": [0], "528752618": [0], "528942033": [0], "529853785": [0], "529889600": [0], "530079983": [0], "530092955": [0], "530266001": [0, 1], "530481328": [0], "530550990": [0], "530559266": [0], "530562367": [0], "530603337": [0], "530864159": [0], "530894170": [0], "531199886": [0, 1], "531327438": [0], "531390943": [0], "531410454": [0], "532320999": [0], "532358950": [0], "532550227": [0], "532644218": [0], "532794386": [0], "533058006": [0], "533079405": [0], "533087006": [0], "533202275": [0], "533354110": [0], "533472554": [0], "533629309": [0], "533805302": [0, 1], "533957710": [0], "534007452": [0], "534137179": [0], "534233949": [0], "534776352": [0], "535049932": [0], "535636058": [0], "535696132": [0], "535874533": [0], "536061802": [0], "536677512": [0], "536830644": [0], "537115664": [0], "537146382": [0], "537215341": [0], "537226200": [0], "537644802": [0], "537979528": [0], "538103527": [0], "538267734": [0], "538816557": [0, 1], "539070087": [0], "539139357": [0], "539204224": [0], "539455234": [0], "539554609": [0], "539688342": [0], "539702460": [0], "539721644": [0], "540015838": [0], "540320893": [0], "540343300": [0], "540596978": [0], "540658797": [0], "540988812": [0], "541736504": [0], "541902633": [0], "543057229": [0], "543252644": [0], "543302771": [0], "543511156": [0], "545034135": [0], "545291794": [0], "545422534": [0], "545457662": [0], "545616471": [0], "545838102": [0], "545880814": [0], "546001473": [0], "546055181": [0], "546398320": [0], "546503802": [0], "546532662": [0], "546649401": [0, 1], "547158235": [0], "547184755": [0], "547458267": [0], "547644979": [0], "548439896": [0], "548777204": [0], "548894675": [0], "549143440": [0], "549586515": [0], "549633101": [0], "549809186": [0], "549853492": [0], "549862969": [0, 1], "549974398": [0], "550151369": [0], "550196619": [0], "550255155": [0], "550325330": [0], "550528292": [0], "550703999": [0], "550780640": [0], "551176071": [0], "551337561": [0], "551951784": [0], "552065958": [0], "552069318": [0, 1], "552069454": [0], "552191077": [0], "552226420": [0], "552365240": [0], "552376178": [0], "552463520": [0], "552822856": [0], "552837557": [0, 1], "552937996": [0], "552995187": [0], "553450909": [0], "553698762": [0], "553803417": [0], "554566359": [0], "554877259": [0, 1], "554883372": [0], "555341408": [0], "555885575": [0, 1], "556156368": [0], "556490650": [0], "556833571": [0], "557123654": [0], "557368878": [0], "557462157": [0], "557587740": [0], "558149359": [0, 1], "558233867": [0], "558620918": [0], "559182034": [0, 1], "559195890": [0], "559236028": [0], "559523376": [0], "559878977": [0], "559964116": [0], "560421177": [0], "560564854": [0], "560610446": [0], "560755477": [0], "560830036": [0], "560869501": [0], "561547445": [0, 1], "561871259": [0], "562953584": [0], "562957618": [0], "563123708": [0], "563513229": [0], "563561983": [0], "564121131": [0], "564243551": [0], "564249564": [0], "564797613": [0], "564903589": [0], "565264166": [0], "565342144": [0], "565397720": [0], "565500396": [0], "565749092": [0], "565854201": [0], "565956575": [0], "566259252": [0], "566335955": [0, 1], "566963387": [0], "567016803": [0], "567130645": [0], "567195239": [0], "567324867": [0], "567437149": [0], "567541104": [0], "567654294": [0], "568084904": [0], "568152321": [0], "568267762": [0], "568498071": [0], "568628088": [0], "568755622": [0], "569046167": [0], "569861059": [0], "570739883": [0], "571345741": [0], "571618207": [0], "571774041": [0, 1], "572685242": [0], "572737181": [0], "573420610": [0], "573828075": [0], "574400692": [0], "574679124": [0], "574711224": [0], "574810194": [0], "574984670": [0], "575223573": [0], "575546438": [0], "575651191": [0], "575692086": [0], "576239834": [0], "576388815": [0], "576698908": [0], "577174278": [0], "577454696": [0], "577619406": [0], "577745390": [0], "578298324": [0], "578427489": [0], "579240465": [0], "579353051": [0], "579428384": [0], "579955629": [0], "580600783": [0], "580695763": [0], "580716922": [0], "581108993": [0], "581298772": [0], "581433269": [0], "581608588": [0], "581715403": [0], "581728357": [0], "581781609": [0, 1], "581783554": [0], "581940082": [0], "582135118": [0, 1], "582293467": [0, 1], "582379240": [0], "582517482": [0], "582752589": [0], "583169134": [0], "583201092": [0], "583630734": [0], "584338221": [0], "584347340": [0], "584921957": [0, 1], "584963596": [0], "585117419": [0], "585170657": [0], "585249487": [0], "585572106": [0], "585611555": [0], "585692093": [0], "585737406": [0], "585806031": [0], "585819125": [0], "586082648": [0], "586349954": [0], "586769593": [0], "586877886": [0], "586966082": [0], "587008502": [0], "587061654": [0], "587292497": [0], "587438988": [0], "588306902": [0], "588565626": [0], "588895755": [0], "589060642": [0], "589371465": [0], "589479471": [0], "589695771": [0], "589883766": [0], "590171089": [0], "590311471": [0], "590409485": [0], "590719097": [0], "591012169": [0], "591032679": [0], "591058432": [0], "591079861": [0], "591216489": [0], "591773866": [0], "591773869": [0, 1], "592507817": [0, 1], "592517925": [0], "592739169": [0], "593351289": [0], "594055121": [0], "594201507": [0], "594886185": [0], "594946082": [0], "594952755": [0], "595224378": [0, 1], "595337039": [0], "595361176": [0], "595379234": [0], "595379938": [0], "595696529": [0], "595833006": [0, 1], "595849988": [0], "595923845": [0], "596162707": [0], "596235578": [0], "596838607": [0], "597049538": [0], "597134377": [0], "597170971": [0], "597261771": [0], "597496569": [0], "597669156": [0], "598263117": [0], "598306806": [0], "598790758": [0], "598864644": [0], "598865941": [0], "598977988": [0], "599216574": [0], "599268927": [0], "599495519": [0, 1], "600018940": [0], "600198136": [0], "601319808": [0], "601517747": [0], "601520917": [0], "601715528": [0], "601747659": [0], "601799903": [0], "601976343": [0], "601977847": [0], "602026376": [0], "602233414": [0], "602233543": [0], "602421134": [0], "603010933": [0], "603096725": [0], "603896926": [0], "603898311": [0, 1], "604100702": [0], "604149578": [0], "604178419": [0], "604231793": [0], "604252237": [0], "604463352": [0], "604464725": [0], "604502868": [0], "604566385": [0], "604611167": [0], "605050327": [0], "605551831": [0], "605659358": [0], "605910100": [0], "606174700": [0], "606434878": [0], "606455481": [0], "606764380": [0], "606837036": [0], "607019970": [0, 1], "607244681": [0], "607305870": [0], "607563688": [0, 1], "607835146": [0], "608317735": [0], "609381562": [0], "609543837": [0], "609609351": [0], "609682927": [0], "610312004": [0, 1], "610741121": [0], "610814575": [0, 1], "610921096": [0], "611313675": [0], "611643373": [0], "611644110": [0], "612594384": [0], "612686837": [0], "613202869": [0], "613379900": [0, 1], "613571247": [0], "613729570": [0], "613841341": [0, 1], "615149878": [0], "615185875": [0], "615200654": [0], "615221315": [0], "615279283": [0], "615682097": [0], "615936690": [0], "615983204": [0], "616134165": [0], "616279983": [0], "616407757": [0], "616470217": [0], "616537408": [0], "616680913": [0], "617018165": [0], "617041815": [0], "617072752": [0], "617104948": [0], "617272657": [0], "617472088": [0], "617506812": [0], "617691316": [0], "617758035": [0], "618026391": [0], "618027279": [0], "618321096": [0], "618912554": [0], "618938949": [0], "619328200": [0], "619455725": [0], "619488393": [0], "620317935": [0], "620455651": [0], "620626333": [0], "620796006": [0], "621113547": [0], "621608334": [0], "621943314": [0], "622339711": [0], "622371230": [0], "622420001": [0], "623112144": [0], "623255339": [0, 1], "623957403": [0], "623964265": [0], "623975107": [0], "624055769": [0], "624115386": [0], "624579608": [0], "624841433": [0], "625172847": [0, 1], "625266975": [0], "625284588": [0], "625331333": [0], "625510911": [0], "625683352": [0], "625967633": [0], "626387254": [0], "626702243": [0], "627035745": [0], "627120993": [0], "627234143": [0], "627274843": [0], "628030887": [0], "629016640": [0], "629661830": [0], "629820702": [0], "629983379": [0], "630173388": [0], "630294851": [0], "630534767": [0], "631286233": [0], "631305991": [0, 1], "631356534": [0], "631460397": [0], "631520446": [0], "632336952": [0], "632390506": [0], "632678843": [0], "633147893": [0], "633289114": [0], "633824263": [0], "634428272": [0], "634473022": [0], "634658012": [0], "634900206": [0], "634953619": [0], "635133751": [0], "635501895": [0], "635875049": [0], "636223568": [0, 1], "636286203": [0], "636538595": [0], "636890281": [0], "637055945": [0], "638034707": [0], "638086746": [0], "638488704": [0], "638740410": [0], "638753816": [0], "638818590": [0, 1], "638825066": [0], "638897149": [0], "639152988": [0], "639633438": [0], "639918156": [0], "640041093": [0], "640048600": [0], "640310618": [0], "640321468": [0], "640322038": [0], "640462058": [0], "640729350": [0], "640766143": [0], "641161257": [0], "641350988": [0], "641532977": [0], "641558916": [0], "641565586": [0], "642181990": [0], "642410400": [0], "642419301": [0], "642648415": [0], "642705472": [0], "642785603": [0], "642948641": [0], "643088161": [0], "643187688": [0], "643395458": [0], "643506624": [0], "643791274": [0], "644172440": [0], "644422174": [0], "644543681": [0], "644608044": [0], "644729636": [0], "645238356": [0], "645511122": [0], "645695827": [0], "645789062": [0], "645855060": [0, 1], "646108004": [0], "647055772": [0], "647110667": [0], "647256141": [0], "647273780": [0], "647466341": [0], "647544307": [0], "647598241": [0], "647631879": [0], "647828576": [0], "647895578": [0], "648026631": [0], "648213861": [0], "648424704": [0], "648518397": [0], "648548715": [0], "649240497": [0], "649254437": [0], "649289734": [0, 1], "649445508": [0], "649562635": [0], "649572003": [0], "649615940": [0], "650050366": [0], "650071993": [0], "650149633": [0], "650494330": [0], "650856344": [0], "650930096": [0], "651056057": [0], "651077989": [0], "651382561": [0], "651714622": [0], "651716639": [0], "651828358": [0], "651960571": [0], "652331505": [0], "652423632": [0], "652812049": [0], "652985332": [0], "652987552": [0], "653137327": [0], "653199365": [0], "653202700": [0], "653456467": [0], "654340247": [0, 1], "654770982": [0], "654798325": [0], "654900338": [0], "654921404": [0], "655551179": [0], "655578281": [0], "655612956": [0], "655647909": [0], "655705493": [0, 1], "655738067": [0], "656092547": [0], "656353735": [0], "656716979": [0], "657093136": [0], "657411682": [0], "657429296": [0], "658354017": [0], "658517609": [0], "658619469": [0], "658692633": [0], "658974620": [0], "659360227": [0], "659453616": [0], "659461669": [0], "659470552": [0], "659546416": [0, 1], "659999073": [0], "660067209": [0], "661107788": [0], "661332960": [0], "661436337": [0], "661739834": [0], "661815937": [0, 1], "662417068": [0], "662467381": [0], "662491858": [0], "662719848": [0], "663151947": [0], "663179487": [0], "663220457": [0], "663606076": [0], "663824561": [0], "663984082": [0], "664185048": [0], "664226787": [0], "664282351": [0], "664467177": [0], "664477099": [0], "664562169": [0], "664616410": [0], "664700721": [0], "664704162": [0, 1], "664972058": [0, 1], "665370435": [0], "666068098": [0], "666152819": [0], "666295297": [0], "666399550": [0], "666428932": [0], "667676819": [0], "667731514": [0], "667934645": [0], "667966635": [0], "668058446": [0], "668300610": [0], "668301375": [0], "668500461": [0], "668636452": [0], "668768330": [0], "669165215": [0], "669267540": [0], "669605879": [0], "670057638": [0], "670271323": [0], "670440940": [0], "670548986": [0], "670959663": [0], "670995774": [0], "671112661": [0], "671227995": [0], "671255893": [0], "671293820": [0], "671732462": [0], "672056062": [0], "672312753": [0], "672338539": [0], "672396338": [0, 1], "672403649": [0, 1], "672944687": [0, 1], "673259336": [0], "673294552": [0], "673391951": [0], "673639245": [0], "673739778": [0], "673886039": [0], "673932128": [0], "673945036": [0], "674140260": [0], "674191182": [0], "674265932": [0, 1], "674454567": [0], "674534124": [0], "674868982": [0], "675102531": [0], "675690710": [0], "675703250": [0], "675900033": [0], "675911689": [0], "676029780": [0], "676322025": [0], "676473389": [0], "676803490": [0], "676925150": [0], "677035752": [0], "677054924": [0], "677336635": [0], "677631603": [0], "678178447": [0], "678627499": [0], "678864642": [0], "679140717": [0], "679564686": [0], "679574910": [0], "680524307": [0], "681075213": [0], "681262321": [0], "681275246": [0], "681617025": [0], "681922647": [0], "682021051": [0], "682335131": [0], "682341680": [0], "682610807": [0, 1], "683291352": [0], "683581871": [0, 1], "683720300": [0], "683921909": [0], "684091988": [0], "684652779": [0, 1], "684839873": [0], "685012154": [0], "685188414": [0], "685251127": [0], "685336448": [0], "685757942": [0], "685797913": [0], "685955506": [0], "686598147": [0], "686776195": [0], "686913340": [0], "687034150": [0], "687143725": [0], "687739194": [0], "687983898": [0], "688014483": [0], "688213643": [0], "688346604": [0], "688679261": [0, 1], "688701935": [0], "688726636": [0], "688905196": [0], "689173726": [0], "689213009": [0], "689252779": [0], "689257409": [0], "689859122": [0], "690089442": [0], "690198792": [0], "690307261": [0], "690672429": [0], "690685329": [0], "690690187": [0], "690766799": [0], "691046500": [0], "691150452": [0], "691403960": [0], "691479299": [0], "691654601": [0], "691906991": [0], "691931607": [0], "692006416": [0], "692684120": [0], "692924453": [0], "693009395": [0], "693864198": [0], "694194347": [0], "694277756": [0], "694804516": [0], "694832338": [0], "694960383": [0], "695308241": [0], "695345735": [0], "695381718": [0, 1], "695408043": [0], "695663086": [0], "695685136": [0], "696539805": [0], "696641381": [0], "696662473": [0, 1], "696745620": [0], "697050685": [0], "697090611": [0], "697255374": [0], "697570929": [0], "697660056": [0], "697887502": [0], "698016663": [0], "698156837": [0], "698608122": [0], "698728563": [0], "699258424": [0, 1], "699744134": [0], "700503612": [0], "700643111": [0], "700755172": [0], "700915293": [0], "700955543": [0], "701027273": [0], "701539568": [0], "701776934": [0], "702304598": [0], "702476203": [0], "702589198": [0], "702591703": [0], "702729856": [0], "702826885": [0], "703104449": [0], "703305948": [0], "703384662": [0], "703595598": [0], "703973136": [0, 1], "704039092": [0], "704401999": [0], "704637932": [0], "704984975": [0], "705009728": [0], "705081468": [0], "705096160": [0], "705458013": [0], "705496832": [0], "705549320": [0], "705840154": [0], "706089615": [0], "706106909": [0], "706350843": [0], "706457920": [0], "706719005": [0], "706992705": [0], "707198875": [0], "707398093": [0], "707524310": [0], "707654227": [0], "707708804": [0], "707940700": [0], "708097287": [0], "708212005": [0], "708699177": [0], "709122084": [0], "709171997": [0], "709490237": [0], "709963281": [0], "709988799": [0], "710360022": [0], "710706924": [0], "710883573": [0], "710946621": [0], "710951686": [0], "711033943": [0], "711070049": [0], "711155162": [0], "711260003": [0], "711345926": [0], "711477364": [0], "711569239": [0], "711670075": [0], "713196976": [0], "713216044": [0], "713477922": [0], "714169639": [0], "714445737": [0], "715038481": [0], "715107162": [0], "715186911": [0], "715267526": [0], "715354141": [0], "715475334": [0], "715536866": [0], "715616569": [0], "715642128": [0], "715992189": [0], "716322338": [0], "716347799": [0], "716456627": [0], "716658365": [0], "716785312": [0], "717024897": [0], "717327874": [0], "717511100": [0], "717729422": [0], "717807998": [0], "717812418": [0], "717931771": [0], "718215939": [0], "718329592": [0], "718854700": [0], "719417975": [0], "719507284": [0], "719782315": [0], "719818349": [0], "719888097": [0], "719920210": [0], "719953224": [0], "720030588": [0], "720608536": [0, 1], "720884823": [0], "720890655": [0], "721002383": [0], "721343875": [0], "721851651": [0], "721988698": [0], "722491144": [0], "722513642": [0], "722676370": [0, 1], "722764957": [0], "723284623": [0], "723474382": [0], "723823293": [0], "724038641": [0], "724260145": [0], "724443104": [0], "724468204": [0], "724539017": [0], "724650313": [0], "725104507": [0], "725124772": [0], "725308424": [0], "725483775": [0], "725499739": [0], "725544060": [0], "725993539": [0], "726225283": [0], "726532706": [0], "726772408": [0], "726823936": [0], "727088046": [0], "727521513": [0], "728213957": [0], "728273404": [0], "728461880": [0], "728578936": [0], "728743046": [0], "728774702": [0], "728843321": [0, 1], "728875898": [0], "729211346": [0], "729371564": [0], "729409187": [0], "729555341": [0], "730298039": [0], "730392655": [0], "730716858": [0], "730756631": [0], "730849655": [0], "730974292": [0], "731011091": [0], "731150422": [0, 1], "731563795": [0], "731812473": [0], "731952514": [0], "732244579": [0], "732502161": [0], "732503122": [0], "732516351": [0, 1], "732531921": [0, 1], "733021054": [0], "733112888": [0], "733437542": [0], "733692597": [0], "733753402": [0], "733769934": [0], "734109570": [0], "734314570": [0], "734326593": [0], "734507199": [0], "734682198": [0, 1], "734902131": [0], "734999262": [0], "735013522": [0], "735060648": [0], "735090270": [0], "735683251": [0], "735766313": [0], "735887007": [0], "735898949": [0, 1], "735930068": [0], "735937229": [0], "736057916": [0], "736062503": [0], "736497602": [0], "736611384": [0], "736689278": [0], "736841410": [0], "736961030": [0], "737025437": [0, 1], "738097996": [0], "738166755": [0], "738298704": [0, 1], "738393734": [0], "738465905": [0], "738672072": [0], "738995201": [0, 1], "739036223": [0], "739123603": [0], "739623755": [0], "739658661": [0], "739884501": [0], "739911884": [0], "740203839": [0], "740258477": [0], "740399148": [0], "740659079": [0, 1], "740964101": [0], "740973799": [0], "741657735": [0], "741881384": [0], "741948236": [0, 1], "742327223": [0], "742607986": [0, 1], "742685496": [0], "742785993": [0], "743169671": [0], "743230125": [0], "743517345": [0], "743790309": [0], "744252651": [0], "744498808": [0], "744682428": [0], "744855377": [0], "745078505": [0], "745514319": [0], "745580811": [0], "745725729": [0], "746121291": [0], "746175142": [0], "746656106": [0], "746674974": [0], "747745326": [0, 1], "748381554": [0], "750204135": [0], "750262473": [0], "751028897": [0], "751488689": [0], "751578276": [0], "751583748": [0], "752202499": [0], "753011549": [0], "753575366": [0], "753924726": [0], "754050338": [0, 1], "754052718": [0], "754236329": [0], "754328648": [0], "754556465": [0], "754710721": [0], "754785153": [0], "755109478": [0], "755420621": [0], "756271020": [0], "756499974": [0], "756531880": [0], "757300127": [0], "757340054": [0], "757464245": [0, 1], "757718170": [0], "757822799": [0], "757833507": [0], "757990926": [0, 1], "758340686": [0], "759362989": [0], "759438756": [0], "759783059": [0], "760293054": [0], "760439615": [0], "760445147": [0], "761059870": [0], "761274027": [0], "761799111": [0], "761882068": [0, 1], "761955985": [0], "762252019": [0], "762726492": [0], "762895753": [0], "763026401": [0], "763323851": [0], "763757754": [0, 1], "763892824": [0], "763957269": [0], "764026188": [0, 1], "764121003": [0], "764199087": [0, 1], "764422785": [0], "765125323": [0], "765161635": [0], "765338764": [0], "765355974": [0], "765727741": [0, 1], "766811628": [0], "767010894": [0], "767129686": [0], "767201985": [0], "767221391": [0], "767241724": [0], "767840219": [0], "768228605": [0], "768486896": [0], "768584705": [0], "769041166": [0, 1], "769107838": [0], "769157505": [0], "769167522": [0], "769227835": [0], "769239516": [0], "769273587": [0], "769656705": [0], "769781011": [0], "769925613": [0], "770104027": [0], "770347776": [0], "771094173": [0, 1], "771107536": [0], "771271520": [0], "771733034": [0], "771903312": [0], "772046780": [0], "772115343": [0], "772260196": [0], "772593547": [0], "772660324": [0], "773121991": [0], "773149693": [0], "773574168": [0], "773734082": [0], "773805271": [0], "774004098": [0], "774415308": [0], "774521485": [0], "774669401": [0], "775727608": [0], "776795544": [0], "777077054": [0, 1], "777130524": [0], "777243002": [0], "777368322": [0], "777421909": [0], "777791836": [0], "777833606": [0], "778003321": [0], "778084511": [0], "778707703": [0], "778882308": [0], "779321671": [0], "779592201": [0], "779596786": [0], "780457835": [0], "780465493": [0], "780780693": [0], "780889792": [0], "780924727": [0], "781121085": [0], "781244055": [0], "781580069": [0], "781625683": [0], "781726073": [0], "781850122": [0], "781891428": [0], "782449521": [0], "782805084": [0], "782921087": [0], "782946981": [0], "783492325": [0, 1], "783634649": [0], "783937834": [0], "784516801": [0], "784544483": [0], "784584198": [0], "784653812": [0], "784713916": [0], "784740720": [0], "784741627": [0], "785069220": [0], "785090915": [0], "785120255": [0], "785193353": [0, 1], "785237938": [0, 1], "785723566": [0], "785878104": [0], "785881411": [0], "785983503": [0], "786010838": [0], "786167676": [0], "786330215": [0], "786346955": [0], "786481012": [0], "786919501": [0], "787106636": [0], "787156400": [0], "787296866": [0, 1], "787373723": [0], "787450983": [0], "787814735": [0], "788039162": [0], "788894841": [0], "788912902": [0], "789587704": [0], "789767171": [0], "790064767": [0], "790175365": [0], "790217214": [0], "790293848": [0], "790380194": [0], "790381820": [0], "790451332": [0], "790480690": [0], "790646205": [0], "790746751": [0], "791631888": [0], "791693863": [0], "792035824": [0], "792690851": [0], "792928976": [0], "792986145": [0], "793635016": [0], "794701140": [0], "794994619": [0], "795313769": [0], "795896922": [0], "795998829": [0], "796329027": [0, 1], "796362906": [0, 1], "796426620": [0], "796686675": [0], "796733927": [0], "796984629": [0], "797413777": [0], "797621604": [0], "797810744": [0], "798227733": [0], "798279449": [0], "798397020": [0], "798618594": [0], "798934053": [0], "799123326": [0], "799304646": [0], "799748222": [0], "799759269": [0], "799821622": [0], "799998725": [0], "800248914": [0], "800254686": [0], "800329996": [0], "800451110": [0], "800558796": [0, 1], "800689175": [0], "801457739": [0], "802403082": [0, 1], "802662800": [0, 1], "802668615": [0], "802848159": [0], "802995571": [0], "803044173": [0], "803281301": [0], "803374643": [0], "803684533": [0], "803808188": [0], "803903343": [0], "803919303": [0], "803986259": [0], "804000812": [0], "804022371": [0], "804043090": [0], "804917686": [0], "804970562": [0], "805286849": [0], "805664476": [0], "805963909": [0], "806145785": [0], "806300803": [0], "806336415": [0], "806392229": [0], "806869825": [0], "807189896": [0], "807590644": [0], "807673157": [0], "807980153": [0], "808027259": [0], "808084666": [0], "808363009": [0], "808397287": [0], "808746029": [0], "809033396": [0], "809287450": [0], "809606814": [0], "810759117": [0], "811512461": [0, 1], "811602299": [0], "811823515": [0], "812131642": [0], "812440317": [0], "812902910": [0], "812905987": [0], "813057394": [0], "813901679": [0], "814524307": [0], "814948904": [0], "815064557": [0], "815196819": [0], "815807992": [0], "816018287": [0], "816743374": [0], "817124833": [0], "817629952": [0], "818105796": [0], "818485300": [0, 1], "819434121": [0], "819645829": [0], "820365425": [0], "820387001": [0], "820829824": [0], "820877962": [0], "821067096": [0], "821505796": [0], "821604496": [0], "821711139": [0], "821962786": [0], "822058945": [0], "822084160": [0], "822092668": [0], "822169435": [0], "823401051": [0], "823465823": [0], "823555147": [0], "823687375": [0], "824025231": [0], "824123549": [0], "824627425": [0], "825352780": [0], "825369591": [0], "825380935": [0], "825715181": [0], "825759173": [0], "825830818": [0], "826272930": [0], "826324989": [0], "826590306": [0, 1], "826866730": [0], "826895761": [0], "827083237": [0], "827086495": [0], "827158995": [0, 1], "827331440": [0, 1], "827348227": [0], "827947916": [0], "828395631": [0], "828529597": [0], "828884964": [0], "829028624": [0], "829118344": [0], "829623384": [0], "830196026": [0], "830345269": [0], "830686391": [0], "830776536": [0], "831303686": [0], "831363057": [0], "832033475": [0], "832134543": [0], "832273802": [0], "832694117": [0], "833071597": [0], "833516556": [0], "833572362": [0], "833850036": [0], "833983614": [0], "833987143": [0], "834036471": [0], "834247722": [0], "834374984": [0], "834494356": [0], "834540320": [0], "835273616": [0], "835380151": [0], "835489080": [0], "835561910": [0], "835844933": [0], "836051410": [0], "836587193": [0], "837055742": [0], "837072746": [0], "837638668": [0], "837647269": [0], "837824133": [0], "838065243": [0], "838158391": [0], "838244219": [0], "838578819": [0], "838834374": [0, 1], "838904518": [0], "838979037": [0], "839861457": [0], "840183806": [0], "840240303": [0], "841652378": [0], "841758616": [0], "842031089": [0], "842150366": [0], "842208644": [0], "842233722": [0], "842243049": [0], "842369930": [0], "842384738": [0], "842744397": [0], "842982591": [0], "843161565": [0], "843222357": [0], "843297332": [0], "843448824": [0], "843525362": [0], "843726299": [0], "844083699": [0], "844091374": [0], "844191549": [0], "844237898": [0], "844401158": [0], "844530832": [0], "844560568": [0], "844719536": [0], "844889548": [0], "844891850": [0], "845539255": [0, 1], "845620928": [0], "846607505": [0], "846688378": [0], "846886789": [0], "847377373": [0], "847387526": [0], "847413842": [0], "848073035": [0], "848085513": [0], "848134017": [0], "848157261": [0], "848174238": [0], "848242290": [0], "848259442": [0], "848735068": [0], "848753776": [0], "848849844": [0], "848954276": [0], "849291520": [0], "849393417": [0], "849458740": [0], "850466997": [0, 1], "850521314": [0], "850758917": [0], "851527353": [0], "851798745": [0], "851856029": [0], "851969070": [0], "852035937": [0], "852301473": [0], "853212434": [0], "853411677": [0], "853520036": [0], "853552642": [0], "853804205": [0], "853875050": [0], "853963405": [0], "854084264": [0], "854305413": [0], "854555531": [0], "854636055": [0, 1], "854725329": [0], "854734071": [0], "854761845": [0], "854851850": [0], "854960105": [0], "855036028": [0], "855141683": [0, 1], "855649502": [0], "856046738": [0], "856218778": [0], "856462178": [0, 1], "856605535": [0], "856710562": [0], "856825352": [0], "857017406": [0], "857034578": [0], "857285119": [0], "857314500": [0, 1], "857543739": [0], "857691555": [0], "857873603": [0], "858051356": [0], "858252340": [0], "858259822": [0], "858489210": [0], "858503937": [0], "858575241": [0], "858748115": [0], "859034175": [0], "859266692": [0], "859690265": [0], "860069949": [0], "860239100": [0], "860413053": [0], "860517034": [0, 1], "860782039": [0], "861297561": [0], "861915276": [0], "862332551": [0], "863160132": [0], "863205350": [0], "863747809": [0], "863857822": [0], "863960394": [0], "864402143": [0], "864577792": [0], "864838816": [0], "865338668": [0], "865349808": [0], "865499876": [0], "865843817": [0, 1], "866109161": [0], "866121035": [0], "866561114": [0], "866569283": [0], "866707540": [0], "866726669": [0], "866917487": [0], "867207597": [0], "867361562": [0, 1], "867481238": [0], "867536067": [0], "867626132": [0], "867728886": [0, 1], "867732027": [0], "867818437": [0], "867878395": [0], "868119166": [0], "868273402": [0], "868548226": [0], "868777415": [0], "868782607": [0], "869094638": [0], "869448500": [0], "869879505": [0], "870044563": [0], "870946432": [0], "871949073": [0], "872515340": [0], "872957874": [0, 1], "873033460": [0], "873145593": [0], "873514405": [0], "874817015": [0], "875283690": [0], "875307802": [0], "875406918": [0], "875409460": [0], "875476101": [0], "875910001": [0], "875968751": [0], "876528993": [0], "877608395": [0], "877708984": [0], "878333924": [0, 1], "878462223": [0], "878751959": [0], "879200143": [0], "879211034": [0], "879483098": [0], "879484181": [0], "879540518": [0], "879551558": [0], "880172015": [0], "880396451": [0], "881103966": [0], "881281055": [0], "881524743": [0], "881607124": [0], "881780171": [0], "881857715": [0], "881907456": [0], "881918791": [0], "882292309": [0, 1], "882319223": [0], "882413504": [0, 1], "882715916": [0], "882729458": [0], "883299315": [0, 1], "883448552": [0], "883585723": [0], "883651382": [0], "883905178": [0], "884318379": [0], "884345896": [0], "885184593": [0], "885331688": [0], "885398899": [0], "885837978": [0], "885895016": [0, 1], "886024357": [0], "886114048": [0], "886208112": [0], "886558255": [0], "886572120": [0], "886584937": [0], "886823915": [0], "887104066": [0], "887139369": [0], "887219564": [0], "887321540": [0], "887487697": [0], "887664502": [0], "887679344": [0], "887770073": [0], "888094435": [0], "888153025": [0], "888167240": [0], "888235014": [0], "889008628": [0], "889326164": [0], "889351220": [0, 1], "889491533": [0], "889633033": [0], "889881761": [0], "890300653": [0], "890312551": [0], "890334703": [0], "890468455": [0], "890574728": [0], "890827437": [0], "891063061": [0], "891109371": [0], "891161841": [0], "891187018": [0], "891215133": [0], "891269138": [0], "891358279": [0], "891381934": [0], "891478259": [0], "891603461": [0], "891669473": [0], "891775016": [0], "892240939": [0, 1], "893724977": [0], "893771379": [0], "893921874": [0], "894019271": [0], "894035190": [0], "894460454": [0], "894751376": [0], "894940920": [0], "894996755": [0], "895113435": [0], "895119019": [0], "895296213": [0], "895334783": [0], "895335256": [0], "895523021": [0], "895742373": [0], "895848500": [0], "895860109": [0], "895931112": [0], "896895368": [0], "896977047": [0], "897095676": [0], "897464834": [0], "898314422": [0], "898518262": [0], "898825206": [0], "899134500": [0], "899261135": [0], "900148476": [0], "900152899": [0], "900408798": [0, 1], "900446758": [0], "900836510": [0], "900874414": [0], "900962181": [0, 1], "901431113": [0], "902187033": [0], "902223203": [0], "902285817": [0], "902581160": [0], "902854992": [0], "903268318": [0], "903315257": [0, 1], "903350307": [0], "903652847": [0], "903774411": [0], "903884146": [0], "903928385": [0], "903989402": [0], "904083191": [0], "904111568": [0], "904640875": [0], "904744722": [0], "904746126": [0], "904850813": [0], "905230021": [0], "905308739": [0], "905409100": [0], "905508347": [0], "906247454": [0], "906501999": [0], "906635722": [0], "906835898": [0], "907434352": [0], "907522167": [0], "907833518": [0], "907993257": [0], "908015913": [0], "908069522": [0], "908168630": [0], "908480157": [0], "908535109": [0], "908669371": [0], "908677846": [0, 1], "908768263": [0], "908773714": [0], "908981051": [0], "909040519": [0], "909493903": [0], "909765822": [0], "909826828": [0], "910336593": [0], "910353931": [0, 1], "910524948": [0], "910601803": [0], "910668557": [0], "911021482": [0], "911341188": [0], "911699541": [0], "911852067": [0], "911889276": [0], "912046248": [0], "912181158": [0, 1], "912185362": [0], "912379487": [0], "912403412": [0, 1], "913771842": [0], "914121264": [0], "914144596": [0, 1], "914305620": [0], "914881246": [0], "914884146": [0], "915185180": [0], "915402668": [0], "915514504": [0], "915521018": [0], "915670365": [0], "915696988": [0], "915731512": [0], "915910184": [0], "916137632": [0], "916399765": [0], "916407056": [0, 1], "916960550": [0], "916963594": [0], "917298649": [0], "917681987": [0], "918190866": [0], "918266770": [0], "918720627": [0], "918772455": [0], "918814915": [0, 1], "918838724": [0], "918909540": [0], "919405662": [0], "919559357": [0], "919980161": [0], "920666492": [0], "920942052": [0], "921195733": [0], "921477302": [0], "921613909": [0], "921696711": [0], "922012722": [0], "922069311": [0], "923017593": [0], "923026393": [0], "923874721": [0], "923990228": [0], "924012836": [0], "924019068": [0], "924348183": [0], "924463850": [0], "924979758": [0], "925077849": [0], "925383032": [0], "925474332": [0], "925612333": [0], "925993496": [0], "925997171": [0], "926043441": [0], "926119604": [0], "926619745": [0], "926973576": [0], "926977055": [0], "927178320": [0], "927193347": [0], "927412335": [0], "928450092": [0], "928460705": [0], "928673400": [0], "929108004": [0], "929488228": [0], "929693771": [0], "929708964": [0], "929739161": [0], "930331266": [0], "930933096": [0], "931000889": [0], "931376420": [0, 1], "931778485": [0], "931930641": [0, 1], "932059550": [0], "932295212": [0], "932428407": [0], "932603012": [0], "932686751": [0], "932719848": [0], "932904915": [0], "933068421": [0], "933353926": [0], "933568906": [0], "933692792": [0], "933888087": [0], "934074577": [0], "934116969": [0], "934711265": [0], "934738595": [0], "935250060": [0], "935307922": [0], "935840283": [0], "935862800": [0], "935889015": [0], "935896056": [0], "936548381": [0], "936556239": [0], "936625454": [0], "936689320": [0], "936847202": [0], "936885647": [0], "937056384": [0], "937301630": [0], "937439199": [0], "937475338": [0], "937716272": [0], "937972950": [0], "938111288": [0], "938429269": [0, 1], "938438110": [0], "938592487": [0], "938956617": [0], "939001671": [0], "939319790": [0], "939702470": [0], "939926395": [0], "939968110": [0], "940008337": [0], "940027792": [0], "940113386": [0], "940139479": [0], "940405267": [0, 1], "941402171": [0], "941542267": [0], "941597664": [0], "941823044": [0], "942027129": [0], "942070225": [0], "942191296": [0], "942285087": [0], "942475556": [0], "942489302": [0], "942611222": [0], "942907809": [0], "943245938": [0], "943859789": [0], "943937972": [0], "943993709": [0], "944044571": [0], "944107393": [0], "944548718": [0], "944630382": [0, 1], "944759885": [0], "944862110": [0], "944931265": [0], "944939750": [0], "945084840": [0], "946153592": [0], "946434464": [0], "946656907": [0], "946834009": [0], "947219910": [0], "947270634": [0], "947764935": [0], "947808404": [0], "947824030": [0], "948183311": [0], "948303854": [0], "948580886": [0], "948656161": [0], "948771135": [0], "949254613": [0], "949349445": [0], "949541579": [0], "949619204": [0], "950171456": [0], "951119590": [0], "951402936": [0], "952068601": [0], "952155863": [0], "952201406": [0], "952264463": [0], "952376168": [0, 1], "952867363": [0], "953276053": [0], "953317256": [0], "953508944": [0], "953696580": [0], "953749726": [0], "953915935": [0], "954109177": [0], "954296090": [0], "954651793": [0], "954823135": [0], "954913621": [0], "955429163": [0], "956065064": [0], "956408425": [0, 1], "956605916": [0, 1], "956890196": [0], "956951769": [0], "957434726": [0], "957841143": [0], "957913699": [0], "958045220": [0], "958118786": [0, 1], "958145804": [0], "958220629": [0], "958946675": [0], "959220010": [0], "959320651": [0], "959601782": [0], "959911738": [0], "960078695": [0], "960356216": [0], "960377133": [0], "960539092": [0], "960735200": [0], "961022344": [0], "961249334": [0], "961426463": [0], "961503714": [0], "961514248": [0], "962109546": [0], "962123578": [0], "962766256": [0], "962788218": [0], "962822793": [0], "963204683": [0], "963392793": [0], "963630501": [0], "963891081": [0], "963987166": [0], "964449111": [0], "964455410": [0], "964560888": [0], "964710051": [0], "964712522": [0], "964713542": [0], "964857102": [0], "965143011": [0], "965578486": [0], "965662563": [0], "965965415": [0], "966019354": [0], "966202873": [0], "966475251": [0], "966686612": [0], "966991995": [0], "967096253": [0], "967664519": [0], "969008783": [0, 1], "969278932": [0, 1], "969295600": [0], "969377712": [0], "969623039": [0], "969980803": [0], "970606432": [0], "970992603": [0], "971049519": [0], "971056042": [0], "971692925": [0], "972035971": [0], "972198264": [0], "972402684": [0], "972647093": [0, 1], "972724924": [0], "973200842": [0], "973215641": [0], "973381066": [0], "973521518": [0], "973626692": [0], "973945493": [0], "974104658": [0], "974341953": [0], "975059981": [0], "975195514": [0, 1], "975326168": [0], "975660789": [0], "975734088": [0], "975916504": [0], "975958794": [0], "976308434": [0], "976325318": [0], "976436996": [0], "976452432": [0, 1], "976567863": [0], "976903116": [0], "977093037": [0], "977097129": [0], "977426586": [0], "977855956": [0], "978240312": [0], "978392665": [0], "978406949": [0], "979255224": [0], "979528136": [0], "979722793": [0, 1], "979977375": [0], "980407366": [0], "980585385": [0], "980955672": [0], "981021635": [0], "981153743": [0, 1], "981223215": [0], "981387142": [0], "981930987": [0], "982515520": [0], "982622116": [0], "982937802": [0], "983046111": [0], "983478401": [0], "983489705": [0], "983578947": [0], "983963434": [0], "984047239": [0], "984181188": [0], "984395859": [0], "984986296": [0], "985187195": [0], "985274297": [0, 1], "985328235": [0, 1], "985670323": [0], "986147074": [0, 1], "986675008": [0], "986699725": [0], "986957352": [0], "987081571": [0], "987496573": [0], "987558526": [0], "987830085": [0], "987838768": [0], "988052537": [0], "988316791": [0], "988342385": [0], "988408127": [0], "988504857": [0], "989355623": [0], "989400249": [0], "990012797": [0], "990552155": [0], "990929681": [0, 1], "991477753": [0], "991695290": [0], "991741356": [0], "991869861": [0, 1], "992434063": [0], "992521239": [0], "992668489": [0], "992798034": [0, 1], "992861455": [0], "993869932": [0], "993895828": [0], "994617985": [0], "994780494": [0], "995086346": [0], "995280233": [0], "995286475": [0], "995347586": [0], "995796738": [0], "995804062": [0], "995818356": [0], "995997504": [0], "996378667": [0, 1], "996751689": [0], "996931036": [0], "997759813": [0], "998150133": [0], "998279493": [0], "998620195": [0], "998645422": [0], "998755152": [0], "998836766": [0], "998921734": [0], "999101344": [0], "999638670": [0], "999740454": [0], "999761305": [0], "1001321704": [0], "1001379588": [0], "1001439864": [0], "1001628054": [0], "1001839249": [0], "1001940800": [0], "1002021539": [0], "1002048722": [0], "1002480048": [0], "1002533876": [0, 1], "1002732107": [0], "1003508382": [0], "1003787141": [0], "1003957327": [0], "1004402393": [0], "1004404730": [0], "1004414856": [0], "1004637313": [0], "1004781383": [0], "1004924355": [0], "1005129105": [0], "1005385830": [0, 1], "1005716842": [0], "1006523598": [0], "1006903223": [0], "1007068642": [0], "1007203602": [0], "1007592495": [0], "1007665556": [0], "1007713803": [0, 1], "1007853491": [0], "1007927305": [0], "1008347794": [0], "1008761029": [0], "1008879279": [0], "1009124101": [0], "1009366361": [0], "1009457746": [0], "1009885979": [0], "1009960562": [0], "1009965986": [0], "1010145043": [0], "1010191081": [0], "1010648678": [0], "1011132552": [0], "1011366626": [0, 1], "1011760379": [0], "1012194657": [0], "1012425835": [0], "1012449714": [0], "1012632282": [0], "1012869728": [0], "1013261524": [0], "1013337206": [0], "1013781328": [0], "1014441315": [0], "1014569233": [0], "1014710491": [0], "1014916849": [0], "1014933783": [0], "1015764264": [0], "1015767529": [0], "1016311996": [0], "1016391394": [0], "1016657076": [0], "1016779882": [0], "1016799555": [0], "1016875129": [0], "1016962163": [0], "1017174958": [0, 1], "1017368557": [0, 1], "1017551285": [0], "1017606307": [0], "1017645186": [0], "1018043519": [0], "1018353911": [0], "1018639577": [0], "1018694034": [0], "1019118223": [0], "1019137477": [0, 1], "1019172035": [0], "1019291684": [0], "1019591558": [0], "1019813644": [0], "1019826575": [0], "1019955764": [0, 1], "1020093984": [0], "1020125184": [0], "1020245565": [0], "1020466574": [0], "1020580717": [0], "1020746448": [0], "1020871579": [0], "1021016920": [0], "1021569125": [0, 1], "1021625666": [0], "1022340636": [0, 1], "1022614221": [0], "1022638276": [0], "1022817902": [0], "1022908576": [0], "1022981393": [0], "1023657402": [0], "1023713785": [0], "1024633652": [0], "1025132028": [0], "1025276735": [0], "1025284110": [0], "1025331163": [0], "1025650932": [0], "1025724420": [0], "1026190659": [0], "1026444429": [0, 1], "1026456357": [0], "1026489497": [0, 1], "1027002839": [0], "1027015416": [0], "1027083067": [0], "1027142174": [0], "1027195587": [0], "1027389205": [0], "1027467567": [0], "1027748880": [0], "1028090059": [0], "1028334301": [0], "1028484905": [0], "1028702872": [0], "1029185332": [0], "1029974028": [0], "1030338502": [0], "1030603328": [0], "1030605858": [0], "1030755249": [0], "1031214029": [0], "1031609111": [0], "1031700995": [0], "1031780164": [0], "1031808887": [0], "1032077126": [0, 1], "1032243556": [0], "1032575492": [0], "1032618472": [0], "1032944455": [0], "1033091107": [0], "1033723890": [0], "1033889273": [0, 1], "1033978554": [0], "1034246254": [0], "1034557333": [0], "1034750700": [0], "1034789190": [0], "1035183004": [0], "1035347266": [0], "1035392140": [0], "1035441192": [0], "1035504263": [0], "1035641299": [0], "1035668174": [0], "1035866463": [0], "1036033475": [0], "1036728498": [0], "1036899221": [0], "1036986092": [0], "1037705965": [0], "1037771593": [0], "1037986054": [0], "1038024769": [0], "1038431889": [0], "1038735411": [0], "1039213056": [0], "1039271251": [0], "1039682822": [0], "1040234634": [0], "1040333046": [0], "1040335423": [0], "1040374306": [0], "1040429961": [0], "1040693197": [0], "1040925141": [0], "1041130262": [0], "1041202164": [0], "1041862021": [0], "1042087334": [0], "1042112998": [0], "1042505492": [0], "1042787669": [0], "1043012980": [0], "1043065353": [0], "1043259152": [0], "1043551286": [0], "1043931806": [0], "1044386546": [0], "1044572174": [0], "1044592796": [0], "1044685710": [0], "1044855948": [0], "1044967296": [0], "1044970843": [0], "1045671912": [0], "1046255719": [0], "1046630579": [0], "1046810449": [0], "1047075859": [0], "1047283463": [0], "1048061405": [0], "1048229518": [0], "1048611277": [0], "1048786046": [0], "1049008249": [0], "1050017091": [0], "1050206791": [0], "1050230956": [0], "1050246959": [0], "1050450435": [0], "1050924448": [0], "1051269891": [0], "1052146037": [0], "1052252659": [0], "1052674580": [0], "1052883465": [0], "1053633501": [0], "1053651932": [0], "1053726436": [0], "1053791993": [0], "1053945305": [0], "1054325811": [0], "1054527164": [0], "1054595658": [0], "1054646610": [0], "1054786609": [0, 1], "1055085816": [0], "1055513774": [0], "1055573766": [0], "1055640610": [0], "1056407955": [0], "1056458939": [0], "1056626485": [0], "1056916875": [0], "1056929908": [0], "1057164815": [0], "1057234496": [0], "1057988182": [0, 1], "1057990853": [0], "1058095917": [0], "1058250386": [0], "1058427974": [0], "1058428454": [0], "1058498181": [0], "1059032253": [0], "1059166610": [0], "1059473661": [0], "1059734488": [0], "1060097092": [0], "1060222930": [0], "1060324714": [0], "1060740194": [0], "1060773690": [0], "1060837347": [0], "1060895953": [0], "1061026664": [0], "1061096141": [0], "1061251555": [0], "1061516794": [0], "1061595949": [0], "1061635143": [0], "1061893653": [0], "1062110280": [0, 1], "1062313634": [0], "1062475239": [0], "1062748151": [0], "1062910340": [0], "1063026619": [0], "1063088693": [0], "1063408439": [0], "1063479487": [0], "1063766075": [0], "1064170935": [0], "1064324202": [0], "1064339179": [0], "1064412409": [0], "1064728241": [0], "1064912006": [0], "1064965474": [0], "1065275713": [0], "1065724953": [0], "1066024311": [0], "1066474054": [0], "1066678208": [0, 1], "1066719973": [0], "1066826043": [0], "1067016169": [0], "1067017122": [0], "1067433764": [0], "1067530172": [0], "1067784250": [0], "1067933756": [0], "1068130840": [0], "1069613056": [0, 1], "1069629787": [0], "1069952809": [0], "1070094867": [0], "1070147528": [0], "1070423795": [0], "1070501206": [0], "1070641524": [0], "1070681391": [0], "1070709067": [0], "1070919811": [0], "1071306940": [0], "1071625287": [0], "1071674582": [0], "1071951690": [0], "1072124350": [0], "1072209210": [0], "1072871021": [0, 1], "1073819557": [0], "1073864941": [0], "1074162609": [0], "1074245044": [0], "1074553117": [0], "1074909897": [0, 1], "1075376757": [0], "1075401985": [0], "1075603568": [0], "1075610990": [0], "1075662188": [0], "1076033196": [0], "1076375652": [0], "1076478212": [0], "1076620079": [0], "1076669911": [0, 1], "1076806480": [0], "1077022445": [0], "1077116183": [0], "1077135907": [0], "1077260773": [0], "1077524580": [0], "1077781545": [0], "1077913570": [0], "1078460162": [0], "1078623712": [0], "1079027209": [0], "1079191280": [0], "1079392409": [0], "1079416456": [0], "1079444177": [0], "1079608902": [0], "1079921172": [0], "1080274556": [0], "1080542429": [0], "1081004638": [0], "1081045979": [0], "1081137638": [0], "1081156235": [0], "1081417698": [0], "1081627805": [0], "1081671496": [0], "1081784052": [0], "1081875845": [0, 1], "1081933930": [0], "1081956911": [0], "1082154133": [0], "1082186351": [0, 1], "1082346379": [0], "1082461320": [0], "1082615620": [0], "1083522093": [0], "1083564178": [0], "1083654127": [0], "1083674102": [0], "1083772754": [0], "1083829590": [0], "1084311592": [0], "1084409094": [0], "1084423374": [0], "1084463968": [0], "1084898238": [0], "1084996718": [0], "1085148763": [0], "1085176158": [0], "1085253891": [0], "1085347750": [0], "1085524274": [0], "1085853575": [0, 1], "1085938795": [0], "1085981029": [0], "1086326539": [0], "1086383545": [0], "1086497801": [0], "1086509443": [0], "1086584413": [0], "1086804959": [0], "1087107746": [0], "1087224800": [0], "1087246203": [0], "1087471183": [0], "1087566834": [0], "1087611256": [0], "1088181610": [0], "1088199148": [0], "1088312211": [0], "1088336960": [0], "1088486994": [0], "1088685646": [0], "1088841058": [0], "1089136257": [0], "1089216930": [0], "1089248832": [0], "1089293858": [0], "1089548926": [0], "1090285235": [0], "1090501053": [0], "1090557401": [0], "1090811858": [0], "1091139102": [0], "1091263381": [0], "1091620435": [0], "1091694562": [0], "1091753713": [0], "1091800078": [0], "1091851273": [0], "1092288497": [0], "1092463518": [0], "1093062389": [0], "1093115550": [0], "1093179429": [0], "1093209510": [0], "1093425554": [0], "1093768189": [0], "1093947693": [0], "1094000974": [0], "1094037331": [0], "1094732219": [0], "1095265101": [0], "1095381162": [0], "1095440820": [0], "1095485734": [0], "1095533217": [0], "1095537240": [0, 1], "1095690405": [0], "1095839614": [0], "1096047865": [0], "1096436039": [0], "1096808458": [0], "1096818669": [0, 1], "1096860313": [0], "1097484072": [0], "1097746459": [0], "1098005491": [0], "1098501905": [0], "1098529926": [0], "1098690479": [0], "1098852493": [0], "1099049304": [0], "1099108173": [0], "1099194238": [0], "1099354992": [0], "1099505255": [0], "1099548088": [0], "1100014184": [0], "1100237998": [0], "1100245951": [0], "1100609729": [0], "1101027910": [0], "1101156350": [0], "1101516277": [0], "1102058992": [0], "1102224387": [0], "1102225807": [0], "1102454126": [0], "1102807357": [0], "1102966095": [0], "1103467230": [0], "1103488371": [0], "1103524716": [0], "1103915756": [0], "1104332885": [0], "1104391256": [0], "1104911057": [0], "1105236965": [0], "1105660433": [0], "1105806318": [0], "1105824183": [0], "1105961340": [0], "1106052114": [0], "1106167897": [0], "1106267814": [0], "1106366979": [0], "1107182560": [0], "1107283088": [0], "1107462932": [0], "1107530094": [0], "1107671697": [0], "1107834178": [0], "1107924739": [0, 1], "1108001341": [0], "1108030458": [0], "1108296269": [0], "1108608333": [0], "1109064362": [0], "1109634523": [0], "1109719117": [0], "1109776806": [0], "1109806339": [0], "1109966523": [0], "1110476555": [0], "1110517959": [0], "1110536866": [0], "1110631618": [0], "1110665772": [0], "1110752199": [0], "1110942214": [0], "1111064667": [0], "1111071955": [0], "1111268325": [0], "1111910866": [0], "1112045744": [0, 1], "1112306911": [0], "1112600696": [0], "1112985147": [0], "1113780070": [0], "1114003339": [0], "1114043742": [0, 1], "1114268043": [0], "1114380013": [0, 1], "1114426603": [0], "1114647611": [0], "1114704621": [0], "1114766557": [0], "1114930921": [0], "1115068094": [0], "1115277544": [0], "1116058404": [0], "1116201959": [0], "1116288795": [0], "1116470998": [0], "1116479642": [0], "1116954243": [0], "1117129614": [0], "1117191180": [0], "1117257441": [0], "1117453190": [0], "1117960821": [0], "1118217753": [0], "1119072566": [0], "1119164004": [0], "1119433914": [0], "1119525164": [0], "1119572304": [0], "1119643772": [0], "1119726295": [0], "1119848097": [0], "1119998851": [0], "1120830595": [0], "1121112895": [0], "1122041276": [0, 1], "1123018542": [0], "1123351701": [0], "1123437844": [0], "1124249988": [0], "1124302035": [0], "1124346051": [0], "1124530826": [0], "1124574582": [0], "1125250281": [0], "1125804894": [0], "1125870120": [0], "1126500633": [0], "1126827577": [0], "1126914912": [0], "1126938487": [0, 1], "1126955140": [0], "1127836696": [0], "1128034694": [0], "1128114488": [0], "1128410094": [0], "1129191551": [0], "1129344668": [0], "1129399128": [0], "1129513632": [0], "1129543757": [0], "1129617338": [0], "1129895803": [0], "1130343402": [0], "1130377418": [0], "1130599379": [0], "1130880791": [0], "1130987733": [0], "1131114042": [0], "1131231818": [0], "1131279135": [0], "1132502971": [0], "1132714048": [0], "1132729550": [0], "1132773168": [0, 1], "1132782521": [0], "1132784821": [0, 1], "1133273412": [0], "1133549992": [0], "1133687943": [0], "1133982658": [0], "1134054930": [0], "1134067844": [0], "1134271334": [0, 1], "1134793801": [0], "1135126065": [0], "1135818486": [0], "1135871862": [0], "1136053250": [0], "1136631178": [0], "1136743062": [0], "1137292267": [0], "1137634747": [0], "1137708667": [0], "1137754680": [0], "1138240781": [0], "1138677916": [0], "1138747569": [0], "1138795272": [0], "1139018666": [0], "1139034625": [0], "1139603417": [0], "1139610541": [0], "1139795169": [0], "1139854235": [0], "1139874109": [0], "1139973603": [0], "1140630064": [0, 1], "1141129708": [0], "1141258931": [0], "1141266634": [0], "1141796599": [0], "1141846828": [0], "1141958168": [0], "1141996138": [0], "1142068242": [0], "1142240670": [0], "1142257633": [0], "1142903083": [0], "1142997506": [0], "1143233578": [0], "1143438182": [0], "1143700024": [0], "1144092947": [0, 1], "1144156039": [0], "1144213362": [0], "1144393411": [0], "1144698841": [0], "1144893851": [0], "1144932134": [0], "1144958726": [0, 1], "1145001230": [0], "1145055573": [0], "1145547480": [0], "1145617744": [0], "1145707206": [0], "1146185446": [0], "1146348640": [0], "1146554698": [0], "1146705637": [0], "1146731587": [0, 1], "1146928807": [0], "1146941516": [0], "1147184191": [0], "1147263168": [0], "1147454481": [0], "1147834924": [0], "1148371391": [0, 1], "1149042163": [0], "1149419835": [0], "1149434948": [0], "1149800107": [0], "1149858271": [0], "1149964515": [0], "1149993154": [0], "1150191201": [0], "1150250029": [0], "1150257607": [0], "1150771679": [0], "1150810199": [0], "1151124153": [0], "1151232444": [0], "1151402172": [0], "1151483734": [0], "1151842009": [0], "1151853184": [0], "1151893470": [0], "1152201937": [0], "1152251204": [0], "1152540986": [0], "1152576928": [0], "1152617188": [0], "1152653418": [0], "1152816023": [0], "1153199694": [0], "1153652093": [0], "1153789489": [0], "1153839350": [0], "1154433592": [0], "1154715850": [0], "1154820509": [0], "1155428815": [0], "1155523168": [0], "1155801624": [0], "1155901301": [0], "1156711004": [0], "1156821103": [0], "1157425675": [0], "1157646626": [0], "1157781806": [0], "1157944553": [0], "1158074926": [0], "1158192732": [0], "1158528617": [0], "1158635803": [0, 1], "1158795410": [0], "1158971690": [0], "1159439932": [0], "1159721449": [0], "1159807657": [0], "1159878588": [0], "1160066590": [0, 1], "1160098501": [0, 1], "1160749108": [0], "1161007240": [0], "1161223128": [0], "1161265800": [0], "1161496522": [0], "1161751815": [0], "1161840099": [0], "1162287035": [0], "1162338270": [0], "1163251439": [0], "1163269852": [0], "1163837800": [0], "1163855995": [0], "1164180201": [0], "1164640014": [0], "1164754507": [0], "1165190597": [0], "1165200997": [0], "1165336550": [0], "1165862978": [0], "1166005492": [0], "1166179146": [0], "1166823604": [0], "1166876093": [0], "1167095990": [0], "1167525040": [0], "1167525740": [0], "1167574470": [0], "1167589978": [0], "1167599699": [0], "1167660003": [0], "1168328085": [0], "1168413288": [0], "1168559836": [0], "1168819154": [0], "1168897654": [0], "1169039746": [0], "1169091497": [0], "1169127760": [0], "1169287625": [0], "1169803901": [0], "1169943221": [0], "1169948392": [0], "1170142828": [0], "1170248617": [0, 1], "1170583284": [0], "1170614179": [0], "1170656221": [0], "1170775879": [0], "1170778152": [0], "1171351949": [0], "1171437287": [0], "1171652344": [0], "1172316338": [0], "1172533533": [0], "1172698971": [0], "1172833650": [0], "1172933560": [0], "1173448157": [0], "1173730780": [0], "1174416203": [0], "1174603693": [0], "1174708262": [0], "1174741392": [0], "1174797944": [0], "1174891637": [0], "1174966055": [0], "1174997245": [0], "1175138349": [0], "1175342061": [0], "1175464017": [0], "1175524808": [0], "1175686219": [0, 1], "1175727164": [0], "1175814164": [0], "1176302842": [0, 1], "1176314427": [0], "1176645356": [0], "1176807112": [0], "1177122323": [0], "1177126323": [0], "1177194111": [0], "1177201966": [0], "1177458954": [0, 1], "1177816768": [0], "1177865920": [0], "1177933804": [0], "1178282336": [0], "1178466829": [0], "1178612279": [0], "1178714831": [0], "1178719329": [0], "1178723478": [0], "1178801954": [0], "1178879173": [0], "1180054197": [0], "1180075504": [0], "1180281871": [0], "1180348440": [0], "1180413056": [0], "1180501841": [0], "1181337523": [0, 1], "1181551924": [0], "1181658279": [0], "1182726028": [0], "1182958203": [0], "1182994491": [0], "1183213178": [0], "1183514062": [0], "1183790571": [0], "1184051727": [0], "1184475283": [0], "1184594214": [0], "1185223037": [0], "1185796428": [0], "1186198313": [0], "1186354660": [0], "1186579889": [0], "1186605878": [0], "1187347270": [0], "1187588399": [0], "1187825793": [0], "1187951309": [0], "1188256050": [0], "1188300321": [0], "1188380708": [0], "1188948225": [0], "1189079918": [0], "1189187797": [0], "1189474430": [0], "1189777355": [0], "1189877072": [0], "1190299362": [0], "1190744374": [0], "1191390253": [0], "1191402651": [0], "1191594663": [0], "1192028488": [0], "1192683173": [0], "1192868071": [0], "1192943808": [0], "1193005586": [0], "1193164170": [0], "1193187154": [0], "1193224615": [0], "1194345989": [0], "1194682790": [0, 1], "1195220320": [0], "1195319105": [0, 1], "1195506231": [0], "1195655539": [0], "1195785922": [0], "1195934782": [0], "1196128928": [0], "1196240030": [0], "1196382759": [0], "1196575159": [0], "1196590957": [0], "1196826917": [0], "1197057496": [0], "1197084631": [0], "1197122521": [0], "1197698735": [0], "1197915655": [0], "1198087769": [0], "1198129497": [0], "1198327027": [0], "1198628042": [0], "1198720611": [0], "1199312698": [0], "1200353220": [0], "1200525702": [0], "1200556287": [0], "1200600810": [0, 1], "1200629228": [0], "1200643843": [0], "1200817001": [0], "1201076961": [0], "1201127101": [0], "1201197373": [0], "1201766600": [0], "1201970333": [0], "1202264884": [0], "1202544641": [0, 1], "1202692254": [0], "1202833871": [0], "1202885063": [0], "1203212722": [0], "1203314960": [0], "1203361057": [0], "1203373524": [0], "1203585655": [0], "1203715511": [0], "1203764156": [0], "1203780435": [0], "1203802163": [0], "1203881123": [0], "1203929684": [0], "1204060962": [0], "1204832815": [0], "1205118459": [0], "1205215798": [0], "1205579560": [0], "1206026635": [0], "1206093401": [0], "1206401877": [0], "1206812696": [0], "1206928635": [0, 1], "1207069085": [0], "1207108828": [0, 1], "1207297260": [0], "1207412742": [0], "1208158300": [0], "1208234166": [0], "1208235485": [0], "1208320048": [0], "1208407558": [0], "1208561267": [0], "1210083808": [0], "1210215905": [0], "1210538624": [0, 1], "1210727161": [0], "1210903338": [0], "1211061272": [0], "1211096388": [0], "1211508367": [0], "1211609958": [0], "1211723640": [0], "1212266098": [0], "1212302941": [0, 1], "1212347271": [0], "1212355493": [0], "1212479302": [0], "1212584260": [0], "1212672819": [0], "1212848063": [0, 1], "1212889643": [0], "1213442084": [0], "1213593062": [0], "1213896111": [0], "1214341135": [0], "1214447025": [0], "1214623489": [0, 1], "1214697558": [0, 1], "1214843141": [0], "1215866112": [0], "1216278407": [0], "1216588487": [0], "1216594517": [0], "1216660467": [0], "1216715653": [0], "1216906944": [0], "1216950303": [0], "1217011240": [0], "1217080209": [0], "1217098943": [0], "1217193523": [0], "1217286262": [0], "1218253784": [0], "1218712449": [0], "1218880713": [0], "1219210669": [0], "1219217293": [0], "1219253268": [0], "1219588628": [0], "1219883946": [0], "1220199342": [0], "1220282975": [0], "1220293223": [0], "1220293637": [0], "1220421701": [0], "1220504606": [0], "1220540396": [0], "1220723535": [0], "1220820713": [0], "1221014965": [0], "1221086724": [0], "1221116652": [0], "1221233489": [0], "1221271994": [0], "1221286068": [0], "1221304624": [0], "1221523434": [0], "1221728149": [0], "1221939122": [0], "1222686065": [0], "1222686487": [0], "1222818674": [0], "1223029075": [0], "1223157820": [0], "1223229698": [0], "1223354641": [0], "1223431532": [0], "1223883955": [0, 1], "1224194050": [0], "1224395836": [0], "1224454427": [0], "1224477055": [0], "1224493415": [0], "1224591673": [0, 1], "1224817075": [0], "1224916249": [0], "1224929473": [0], "1225620834": [0], "1225726656": [0], "1225742635": [0], "1225791482": [0], "1225987897": [0], "1225992133": [0], "1226111743": [0], "1226557317": [0], "1227280931": [0], "1227338623": [0], "1227535473": [0], "1227693695": [0], "1227818402": [0], "1227966556": [0], "1228655780": [0], "1228941255": [0], "1229072851": [0], "1229260061": [0, 1], "1229356632": [0], "1229716664": [0], "1230176892": [0], "1230351072": [0], "1230389522": [0], "1230454377": [0], "1230565183": [0], "1230655753": [0, 1], "1231353018": [0], "1231470595": [0], "1231622061": [0], "1232235085": [0], "1232379346": [0], "1232529554": [0], "1232594991": [0], "1232760047": [0], "1233376873": [0], "1233403435": [0], "1233638800": [0], "1233664413": [0], "1233808587": [0], "1235032809": [0], "1235606504": [0], "1236328654": [0, 1], "1236508099": [0], "1236514645": [0], "1237308377": [0], "1237496085": [0], "1237525746": [0], "1237753516": [0], "1238263679": [0], "1238346888": [0], "1238605045": [0], "1238901541": [0], "1239015424": [0], "1239595157": [0], "1239657886": [0], "1239730200": [0], "1239736416": [0], "1239846231": [0], "1239953012": [0], "1240148037": [0, 1], "1240403939": [0], "1240564809": [0], "1240743760": [0], "1240973565": [0], "1241353704": [0], "1241422221": [0], "1241821134": [0], "1242075900": [0], "1242194097": [0], "1242505486": [0], "1243213084": [0], "1243239912": [0], "1243513752": [0], "1243534580": [0], "1243604310": [0], "1244642872": [0], "1244777270": [0], "1244780595": [0], "1244852739": [0], "1245415637": [0], "1245490855": [0], "1246020236": [0], "1246036579": [0], "1246087400": [0], "1246619991": [0], "1247332922": [0], "1247355191": [0], "1247598996": [0], "1247653859": [0], "1247756240": [0], "1247789002": [0], "1247999345": [0], "1248042198": [0, 1], "1248714210": [0], "1248881101": [0], "1248884163": [0], "1249001441": [0], "1249292596": [0], "1249383426": [0], "1249393679": [0], "1249437302": [0], "1249829850": [0, 1], "1249956039": [0], "1250001358": [0], "1250145795": [0], "1250213467": [0], "1250437073": [0], "1250703741": [0], "1250743281": [0], "1250921743": [0], "1251008655": [0], "1251048415": [0, 1], "1251179752": [0], "1251236700": [0], "1251378279": [0], "1251716937": [0], "1252496408": [0], "1252759235": [0], "1252876605": [0], "1253028111": [0], "1253177850": [0], "1253478460": [0], "1253935351": [0], "1254243015": [0], "1255175149": [0], "1255475956": [0, 1], "1255746379": [0], "1255811744": [0], "1255957392": [0], "1255991645": [0], "1256023049": [0], "1256031999": [0], "1256171384": [0], "1256554409": [0], "1256939041": [0], "1257063408": [0], "1257090766": [0], "1257120247": [0], "1258165223": [0], "1258170392": [0], "1258433064": [0], "1258569300": [0], "1258840534": [0], "1258862138": [0], "1258945409": [0], "1259519614": [0], "1259573611": [0], "1259858252": [0], "1259975089": [0], "1260326231": [0], "1260443374": [0], "1260885738": [0], "1261021536": [0], "1261069908": [0], "1261215517": [0], "1261254574": [0], "1261280028": [0, 1], "1261343477": [0], "1261384010": [0], "1261567380": [0], "1262267418": [0], "1262868534": [0], "1263252514": [0], "1263449544": [0], "1263896185": [0], "1264282491": [0], "1264396515": [0], "1264448304": [0], "1264546275": [0], "1264628042": [0], "1265108424": [0], "1265247027": [0], "1265452511": [0], "1265508949": [0], "1265565250": [0], "1265811958": [0], "1266005842": [0], "1266066170": [0], "1266132841": [0], "1266227832": [0], "1266357172": [0], "1266854830": [0], "1266950750": [0], "1267063861": [0], "1267226893": [0], "1267331197": [0, 1], "1267394497": [0], "1267703126": [0, 1], "1267811056": [0], "1267834056": [0], "1268236624": [0], "1268550771": [0], "1268621145": [0], "1268733759": [0], "1269221384": [0], "1269286359": [0], "1269654233": [0], "1269749632": [0], "1269756847": [0], "1269848657": [0], "1270257042": [0], "1270504569": [0], "1270839890": [0], "1271648516": [0], "1272556303": [0], "1272628109": [0], "1272716083": [0], "1273015519": [0, 1], "1273120124": [0], "1273779684": [0], "1273885251": [0], "1274007643": [0], "1274272512": [0], "1274336210": [0], "1274460431": [0], "1274899323": [0], "1275706495": [0], "1275775517": [0], "1276106656": [0], "1276199291": [0], "1276288464": [0], "1276376224": [0], "1276386486": [0, 1], "1277052775": [0], "1277069074": [0], "1277504533": [0], "1277603427": [0, 1], "1277886341": [0], "1277966470": [0], "1278310355": [0], "1278492839": [0], "1278772774": [0], "1279391075": [0], "1279516874": [0], "1279838103": [0], "1279935540": [0], "1280317027": [0, 1], "1280340345": [0], "1280577810": [0], "1280700911": [0], "1280787359": [0], "1280916636": [0], "1281357499": [0], "1281469359": [0], "1281725269": [0], "1282126213": [0], "1283079932": [0], "1283106027": [0], "1283296457": [0], "1284082281": [0], "1284252924": [0], "1284257972": [0], "1284359080": [0], "1284660385": [0], "1285496594": [0], "1285509102": [0], "1285690325": [0], "1285737216": [0], "1285816072": [0], "1286500437": [0], "1286939649": [0], "1287367810": [0], "1287461476": [0], "1287475498": [0], "1288231980": [0], "1288477801": [0], "1288808216": [0], "1288820835": [0], "1289031496": [0], "1289039742": [0], "1289086363": [0], "1289153992": [0], "1289199866": [0], "1289269296": [0], "1289557266": [0], "1289845017": [0], "1290163001": [0], "1290211092": [0], "1291227546": [0], "1291335870": [0], "1291480115": [0], "1291586015": [0], "1291698446": [0], "1291866437": [0], "1291918961": [0, 1], "1292041417": [0], "1292205691": [0], "1292760902": [0], "1292960946": [0], "1293036461": [0], "1293322440": [0], "1293560512": [0], "1293576965": [0, 1], "1294223801": [0], "1294237744": [0], "1294371144": [0], "1294490578": [0], "1294518653": [0], "1294792788": [0], "1294923191": [0], "1295002813": [0], "1295010988": [0], "1295358285": [0], "1295399854": [0], "1295493639": [0], "1295731753": [0], "1296251756": [0], "1296443785": [0], "1296687560": [0], "1297101554": [0], "1297316188": [0], "1297501092": [0], "1297901705": [0, 1], "1298081335": [0], "1298825632": [0], "1298838322": [0], "1298901031": [0], "1298993732": [0], "1299523450": [0], "1299560066": [0], "1299692502": [0], "1299742046": [0], "1299841089": [0], "1300063058": [0], "1300087424": [0], "1300099271": [0, 1], "1300249543": [0], "1300277517": [0], "1300337819": [0], "1300400138": [0], "1300495403": [0], "1300751281": [0], "1301159163": [0], "1301338877": [0], "1301459958": [0], "1301486294": [0], "1301757036": [0], "1301837377": [0], "1302496538": [0], "1302597694": [0], "1302754594": [0], "1302839875": [0, 1], "1302870914": [0], "1303399499": [0], "1304711795": [0, 1], "1304754156": [0], "1304888461": [0, 1], "1304964755": [0, 1], "1305385401": [0], "1305494779": [0], "1305928851": [0], "1306466198": [0], "1306617452": [0, 1], "1306986786": [0], "1307004028": [0], "1307174163": [0], "1307891709": [0], "1308068265": [0], "1308087521": [0], "1308400735": [0], "1308401909": [0], "1308435233": [0], "1308726441": [0], "1308839116": [0], "1309364785": [0], "1309420125": [0], "1309423606": [0], "1309670276": [0], "1309705739": [0], "1310359814": [0, 1], "1310422774": [0], "1310455843": [0], "1310987634": [0], "1311011246": [0], "1311440354": [0], "1311445125": [0], "1311478267": [0], "1311759458": [0], "1311772926": [0], "1311820334": [0], "1312312226": [0], "1312346987": [0], "1312410546": [0], "1312779693": [0], "1312847716": [0], "1312882585": [0], "1313258086": [0], "1313366027": [0], "1313466218": [0], "1313689683": [0], "1313830234": [0], "1313939707": [0], "1314331021": [0], "1314520616": [0], "1314525382": [0], "1315411343": [0], "1315663051": [0], "1315933161": [0], "1316230439": [0], "1316484885": [0], "1316583100": [0], "1317303767": [0], "1317561301": [0], "1318315610": [0], "1318673271": [0], "1318798984": [0], "1319409223": [0], "1319545190": [0], "1319638841": [0], "1319876096": [0], "1320004126": [0, 1], "1320419225": [0], "1320756855": [0, 1], "1320845913": [0], "1321159338": [0], "1321400642": [0], "1321660250": [0], "1322258574": [0], "1322515540": [0], "1322582421": [0], "1322656069": [0], "1323113906": [0], "1323220563": [0], "1323236227": [0], "1323947449": [0], "1324044549": [0], "1324048125": [0], "1324223125": [0], "1324399445": [0], "1325155298": [0], "1325186555": [0], "1325338529": [0], "1325453591": [0, 1], "1325837756": [0], "1325971907": [0], "1326234155": [0], "1326447218": [0], "1326528091": [0], "1326528249": [0], "1326587249": [0], "1326588461": [0], "1326689422": [0], "1327026311": [0], "1327047900": [0], "1327670294": [0], "1327944483": [0], "1328170566": [0], "1328214590": [0], "1328531609": [0], "1329191296": [0], "1329586101": [0, 1], "1329617763": [0], "1329716093": [0], "1329806978": [0], "1330122454": [0], "1330620622": [0], "1330647161": [0], "1330906455": [0], "1331228201": [0], "1331281221": [0], "1331501077": [0], "1331727257": [0], "1332023361": [0], "1332896788": [0], "1333327594": [0], "1333371671": [0], "1333382788": [0], "1333415899": [0], "1333744078": [0], "1334134466": [0], "1334524341": [0], "1334581612": [0], "1334734332": [0], "1334818866": [0], "1335273681": [0], "1335823036": [0], "1336316813": [0], "1336805216": [0], "1337614490": [0], "1338012971": [0], "1338253590": [0], "1338263028": [0], "1338287074": [0], "1338591133": [0], "1338749040": [0], "1338937507": [0], "1339184286": [0], "1339286665": [0], "1339428917": [0], "1339517019": [0], "1339541992": [0], "1340106759": [0], "1340512098": [0], "1340869149": [0], "1341012169": [0], "1341084769": [0], "1341384704": [0], "1341406716": [0], "1341451355": [0], "1341492616": [0], "1342354426": [0], "1342424954": [0], "1342621423": [0], "1342623428": [0], "1342680306": [0], "1342768092": [0], "1342922531": [0], "1343014929": [0], "1343041890": [0], "1344037207": [0], "1344365176": [0], "1344435402": [0], "1344457708": [0], "1344486489": [0], "1345017558": [0], "1345033354": [0], "1345099156": [0], "1345446189": [0], "1345645926": [0], "1345698684": [0], "1345823186": [0], "1345901666": [0], "1346407936": [0, 1], "1346430272": [0], "1346622491": [0], "1346636721": [0], "1346957801": [0], "1347230515": [0], "1347386602": [0], "1348049146": [0, 1], "1348081414": [0], "1348709580": [0], "1349084988": [0], "1349191417": [0], "1349312373": [0], "1349537345": [0], "1350179271": [0], "1350244126": [0], "1350523645": [0], "1350577416": [0, 1], "1351366958": [0], "1351382364": [0], "1351543359": [0], "1351731185": [0, 1], "1352370138": [0], "1352484449": [0], "1352577990": [0], "1352974829": [0], "1353218479": [0], "1353387543": [0], "1353486966": [0], "1353542258": [0], "1353550465": [0], "1354099075": [0], "1354119402": [0], "1354318346": [0], "1354486567": [0], "1354625383": [0], "1354637571": [0], "1354799617": [0], "1354989552": [0], "1355477725": [0], "1355605413": [0], "1355660052": [0], "1356006433": [0], "1356049383": [0], "1356129628": [0], "1356305398": [0], "1356495913": [0], "1356805121": [0], "1356883056": [0], "1357105314": [0, 1], "1357742584": [0], "1357787208": [0], "1357963025": [0], "1358185734": [0], "1358423962": [0], "1358752795": [0], "1359616198": [0], "1359954632": [0], "1359981173": [0], "1360090406": [0], "1361113274": [0], "1361380190": [0], "1361528389": [0], "1361683723": [0], "1361843045": [0], "1362248847": [0], "1362720627": [0], "1362727020": [0], "1363032247": [0], "1363534718": [0], "1363635723": [0], "1363655156": [0], "1363928097": [0], "1364004564": [0], "1364569540": [0], "1364784934": [0, 1], "1364812652": [0], "1364899933": [0], "1365035347": [0], "1365118108": [0, 1], "1365124238": [0], "1365207976": [0], "1365614748": [0], "1365744091": [0], "1366171111": [0], "1366234550": [0], "1366562559": [0], "1366831023": [0], "1367048768": [0], "1367167251": [0], "1367572921": [0], "1367938865": [0], "1368039138": [0], "1368170570": [0], "1368480116": [0], "1368645844": [0], "1368777171": [0], "1369674058": [0], "1369842595": [0], "1370560788": [0], "1370608867": [0], "1370824085": [0], "1370830665": [0], "1371306962": [0], "1371378049": [0, 1], "1371389743": [0], "1371392852": [0], "1371638927": [0], "1371679412": [0], "1372000294": [0], "1372874548": [0], "1372922935": [0], "1373143948": [0], "1374318663": [0], "1374321762": [0], "1374443872": [0], "1374632520": [0], "1374685365": [0], "1375366257": [0], "1375846873": [0], "1376236455": [0], "1376573797": [0], "1376641738": [0], "1376645633": [0], "1376782440": [0], "1377305519": [0], "1377392628": [0], "1377411784": [0], "1377480264": [0, 1], "1377560902": [0], "1377654142": [0], "1377675241": [0], "1377961023": [0], "1377961246": [0], "1378023485": [0], "1378575272": [0], "1378667706": [0], "1379015963": [0], "1379079122": [0], "1379170426": [0], "1379454752": [0], "1379551628": [0, 1], "1379581579": [0], "1380287247": [0], "1380526949": [0, 1], "1380879733": [0], "1381160079": [0], "1381509891": [0], "1381535543": [0], "1381567860": [0, 1], "1381979651": [0], "1382346019": [0], "1382678845": [0], "1382854836": [0], "1383074885": [0], "1383103616": [0], "1383321360": [0], "1383538994": [0], "1383810049": [0], "1384510561": [0], "1384706638": [0, 1], "1385738628": [0], "1386077156": [0], "1386091811": [0], "1386212627": [0], "1386399716": [0], "1386636585": [0], "1386809689": [0], "1386956502": [0], "1387082977": [0], "1387589986": [0], "1387739075": [0], "1387819789": [0], "1387830858": [0], "1388138045": [0], "1388166897": [0, 1], "1388244450": [0], "1388370947": [0], "1388375553": [0], "1388521584": [0], "1388610433": [0], "1388701012": [0], "1388750354": [0], "1388764603": [0], "1389055744": [0], "1389089673": [0, 1], "1389130430": [0], "1389397330": [0], "1389548025": [0], "1389704398": [0], "1389999345": [0], "1390029114": [0, 1], "1390078737": [0], "1390254406": [0], "1390421021": [0], "1390499995": [0], "1390666234": [0], "1391057679": [0], "1391457057": [0], "1391465562": [0], "1391857706": [0], "1392247829": [0], "1392292471": [0], "1392321288": [0], "1392341747": [0], "1392418357": [0], "1392647501": [0], "1392921765": [0], "1393007536": [0], "1393111808": [0], "1393273224": [0], "1393470509": [0], "1393932504": [0], "1393965020": [0], "1394125488": [0], "1394164548": [0], "1394242273": [0], "1394426210": [0], "1394961181": [0], "1394994614": [0], "1395176843": [0], "1395248586": [0], "1395325254": [0], "1395506454": [0], "1395752408": [0], "1395757288": [0], "1395831257": [0], "1395967038": [0], "1395987861": [0], "1396607721": [0], "1396691047": [0], "1396714655": [0], "1397191074": [0], "1397714645": [0], "1397979769": [0], "1398184808": [0], "1398536215": [0], "1398556375": [0], "1399203382": [0], "1399293933": [0], "1399325166": [0], "1399901041": [0], "1399917545": [0], "1399989915": [0], "1400597564": [0], "1400856750": [0], "1401345010": [0], "1401569690": [0], "1401651367": [0], "1402227168": [0], "1402318583": [0], "1402355670": [0], "1402547127": [0], "1403581189": [0], "1403675753": [0], "1404042166": [0], "1404433655": [0], "1404577416": [0], "1404589190": [0], "1404633789": [0], "1405106949": [0], "1405163285": [0], "1405600386": [0], "1405764755": [0], "1405793634": [0], "1406286046": [0], "1406474021": [0, 1], "1406520288": [0], "1406880847": [0], "1406954806": [0], "1406985585": [0], "1407109085": [0], "1407213975": [0], "1407367300": [0], "1407593513": [0, 1], "1407699560": [0], "1407837984": [0], "1407987417": [0], "1408011662": [0], "1408074139": [0], "1409019210": [0], "1409427207": [0], "1409679155": [0], "1410056393": [0], "1410141496": [0], "1410364667": [0], "1411114656": [0], "1411285874": [0], "1411355448": [0], "1411539619": [0], "1411822238": [0], "1412070516": [0], "1412175651": [0], "1412501562": [0], "1412692644": [0], "1412863877": [0], "1413439164": [0], "1413753175": [0], "1413858043": [0], "1414220929": [0], "1414377947": [0], "1414495092": [0, 1], "1414692709": [0], "1414811931": [0], "1415067436": [0], "1415097501": [0], "1415574573": [0], "1415705470": [0], "1415715373": [0], "1416409879": [0], "1416422768": [0], "1416525059": [0], "1416665115": [0], "1416865308": [0], "1417000165": [0, 1], "1417038551": [0], "1417148787": [0, 1], "1417291826": [0], "1417333735": [0], "1417577147": [0], "1417787550": [0], "1417820715": [0], "1418608872": [0], "1418955933": [0], "1419052813": [0], "1419365937": [0], "1419718498": [0], "1419962031": [0], "1420004165": [0], "1420087101": [0], "1420483967": [0], "1420489538": [0], "1420616845": [0], "1420792951": [0], "1420969279": [0], "1420985591": [0], "1421257309": [0], "1421528408": [0], "1421605579": [0], "1421629897": [0], "1421758881": [0], "1421902404": [0], "1422508164": [0], "1422840316": [0], "1423109383": [0], "1423168024": [0, 1], "1423854201": [0], "1423943603": [0], "1424083503": [0], "1424084614": [0], "1424817147": [0], "1426078947": [0], "1426079395": [0], "1426131320": [0, 1], "1426355127": [0], "1426712678": [0], "1426948086": [0], "1427693573": [0], "1427841918": [0], "1427968298": [0], "1428781362": [0], "1428972588": [0], "1429054688": [0], "1429172122": [0], "1431002966": [0], "1431013615": [0], "1431180844": [0], "1431188471": [0], "1431242630": [0], "1431567364": [0], "1431724155": [0], "1431910232": [0], "1431993400": [0], "1432086181": [0, 1], "1432229503": [0, 1], "1432382131": [0], "1432414447": [0], "1432573751": [0], "1433593266": [0], "1434034828": [0], "1434215727": [0], "1434622493": [0], "1434859032": [0], "1435495208": [0], "1435922698": [0], "1436165350": [0], "1436195112": [0], "1436220035": [0], "1436259926": [0], "1436270977": [0], "1436504933": [0], "1436813081": [0], "1437151678": [0], "1437194267": [0, 1], "1437417578": [0], "1437425071": [0, 1], "1437644641": [0], "1438312945": [0], "1438355265": [0], "1438670524": [0], "1438728629": [0], "1439154810": [0], "1439217469": [0], "1439279075": [0], "1439355612": [0], "1439517925": [0], "1439727324": [0], "1440760475": [0, 1], "1440925134": [0], "1441177890": [0], "1441264847": [0], "1441324679": [0], "1441597590": [0], "1441675142": [0], "1441910443": [0], "1442878513": [0], "1443427506": [0, 1], "1443464998": [0], "1443474103": [0], "1443530650": [0], "1443808726": [0, 1], "1444030812": [0], "1444058206": [0], "1444148597": [0], "1444483294": [0], "1444494596": [0], "1444537475": [0], "1444883823": [0], "1445018939": [0], "1445158729": [0], "1445435678": [0], "1445534013": [0], "1445534241": [0], "1445848611": [0], "1445980843": [0], "1446332397": [0], "1446475157": [0], "1446874044": [0], "1447099831": [0], "1447154667": [0], "1447169355": [0], "1447176158": [0], "1447526975": [0], "1447598304": [0], "1447897845": [0], "1447949077": [0], "1448428458": [0], "1448817975": [0], "1449156900": [0], "1449210686": [0], "1449241909": [0], "1449516668": [0, 1], "1449523579": [0], "1449801545": [0], "1450021343": [0], "1450022441": [0], "1450025648": [0], "1450446242": [0], "1450726557": [0], "1450967467": [0], "1451350608": [0], "1451383233": [0, 1], "1451404890": [0], "1451408291": [0], "1451554630": [0], "1452201157": [0], "1452293994": [0], "1452306449": [0, 1], "1452359121": [0], "1452950108": [0, 1], "1453095460": [0], "1453179314": [0], "1453215000": [0], "1453573527": [0], "1453997879": [0], "1454979320": [0], "1455404883": [0, 1], "1455407422": [0], "1455581854": [0], "1456053662": [0], "1456230801": [0], "1456378053": [0], "1456594938": [0], "1456630525": [0], "1456635513": [0], "1456643830": [0], "1456853625": [0], "1456909015": [0], "1456935828": [0], "1457018177": [0], "1457147782": [0], "1457334403": [0], "1457587318": [0], "1457849096": [0], "1458175141": [0], "1458533545": [0], "1458860279": [0], "1458898899": [0], "1459265002": [0], "1459517794": [0], "1459690315": [0], "1460036000": [0], "1460054092": [0], "1460054141": [0], "1460101655": [0], "1460355683": [0], "1460424323": [0], "1460589085": [0], "1460921424": [0], "1461097819": [0], "1461705085": [0], "1462292056": [0], "1462350702": [0], "1462696571": [0], "1462806626": [0], "1463080377": [0], "1463916377": [0], "1463918198": [0], "1463948201": [0], "1464301488": [0], "1464367100": [0], "1464852931": [0], "1465374048": [0], "1465377878": [0], "1465645427": [0], "1465803187": [0], "1466156532": [0], "1466388694": [0], "1466513180": [0], "1466586462": [0], "1466985937": [0, 1], "1467353900": [0], "1467426965": [0], "1467566218": [0], "1467657253": [0], "1467886478": [0], "1468475321": [0], "1468843893": [0], "1469524669": [0], "1469525770": [0], "1469589961": [0], "1469848834": [0], "1470037873": [0], "1470120626": [0], "1470322066": [0], "1470322652": [0], "1470501299": [0], "1471017148": [0], "1471167432": [0], "1471261731": [0], "1471328176": [0], "1471425375": [0], "1471495420": [0], "1471509856": [0], "1471520960": [0], "1471844191": [0], "1471923688": [0], "1472474703": [0], "1472541008": [0], "1472776552": [0], "1473309481": [0], "1473334529": [0], "1473479454": [0], "1473591403": [0], "1473741959": [0], "1474410396": [0], "1474814741": [0], "1475881867": [0], "1475889545": [0], "1476139506": [0], "1476192995": [0], "1476256685": [0], "1476412944": [0], "1477280844": [0], "1477345314": [0], "1478324898": [0], "1478440712": [0], "1478588686": [0], "1478592989": [0], "1479104286": [0], "1479463955": [0, 1], "1479760326": [0], "1479968627": [0], "1480504014": [0], "1480571160": [0], "1480751694": [0], "1481553678": [0], "1481606337": [0], "1481827560": [0], "1481946246": [0], "1482223081": [0], "1482488401": [0], "1483580560": [0], "1483794414": [0], "1484007614": [0], "1484372063": [0, 1], "1484516387": [0], "1484703119": [0], "1485184344": [0], "1485222117": [0], "1485460843": [0], "1485554116": [0], "1486184540": [0], "1486185875": [0], "1486760309": [0], "1487401015": [0], "1487578456": [0], "1487664430": [0], "1487872592": [0], "1487902643": [0], "1487993129": [0], "1488563836": [0], "1488607677": [0], "1488754059": [0, 1], "1488843242": [0], "1488876116": [0], "1489161553": [0], "1489167763": [0], "1489168588": [0], "1489249837": [0], "1489615333": [0, 1], "1489722223": [0], "1489875437": [0], "1490400991": [0], "1490476328": [0], "1490548092": [0], "1490606726": [0], "1491998287": [0], "1492042134": [0], "1492193107": [0], "1492591429": [0], "1493358439": [0], "1493378362": [0], "1493774125": [0, 1], "1494011523": [0], "1494129632": [0], "1494209721": [0], "1494269079": [0, 1], "1494548709": [0], "1494562463": [0], "1495027469": [0], "1495521568": [0], "1495561082": [0], "1495741733": [0], "1495780438": [0], "1496000492": [0], "1496195819": [0, 1], "1496919352": [0], "1497704203": [0], "1497890181": [0], "1497913163": [0], "1497992696": [0], "1498988553": [0], "1499133485": [0], "1499348858": [0], "1499417916": [0], "1499554330": [0], "1499701160": [0], "1500139341": [0], "1500183460": [0, 1], "1501456775": [0], "1501644586": [0], "1501645784": [0], "1501996704": [0], "1502026549": [0], "1502300660": [0], "1502414573": [0], "1502623717": [0], "1503115738": [0], "1503349535": [0], "1503352414": [0], "1503374523": [0], "1503797930": [0], "1503946431": [0], "1504179872": [0], "1504750639": [0], "1505180794": [0], "1505383731": [0], "1505534085": [0], "1505675764": [0], "1505700506": [0], "1506077539": [0, 1], "1506157839": [0], "1506160354": [0], "1506212537": [0], "1506378475": [0], "1506428665": [0], "1506482230": [0], "1506498804": [0], "1506706849": [0], "1507133143": [0, 1], "1507188305": [0], "1507237075": [0], "1507257129": [0], "1507411301": [0], "1507476376": [0], "1507808215": [0], "1509593816": [0], "1509703385": [0], "1509813436": [0], "1510151140": [0], "1510187373": [0], "1510465391": [0], "1510610162": [0], "1510769431": [0], "1510834762": [0, 1], "1510898108": [0], "1510942834": [0], "1511199214": [0], "1511463367": [0], "1511582837": [0], "1511596026": [0], "1512160461": [0], "1512196777": [0], "1512438454": [0], "1512441875": [0], "1512483307": [0], "1513007964": [0], "1513197451": [0], "1513340341": [0], "1513983785": [0], "1514086568": [0, 1], "1514271627": [0], "1514548653": [0], "1515001361": [0], "1515191698": [0], "1515206400": [0], "1515276789": [0], "1515425981": [0], "1516009900": [0], "1516033112": [0], "1516045352": [0], "1516311189": [0], "1516372704": [0], "1516407242": [0], "1516849374": [0], "1516890318": [0], "1517144317": [0], "1517220492": [0], "1517245559": [0], "1517732126": [0], "1517961266": [0], "1518177564": [0], "1518181503": [0], "1518219135": [0], "1518232208": [0], "1518696877": [0], "1518735427": [0], "1518821395": [0, 1], "1519001015": [0], "1519060884": [0], "1519103556": [0], "1519221545": [0], "1519234736": [0], "1519540775": [0], "1520550867": [0], "1520919657": [0], "1520944263": [0], "1521057659": [0], "1521205470": [0], "1521440439": [0, 1], "1522178142": [0], "1522179221": [0], "1522195152": [0], "1522829100": [0], "1522876418": [0], "1522880454": [0], "1522882546": [0], "1522909762": [0], "1523132070": [0, 1], "1523247773": [0], "1523474781": [0], "1523781988": [0], "1524211565": [0], "1524240206": [0], "1524769591": [0], "1524876413": [0], "1525416225": [0], "1525582888": [0], "1525662895": [0], "1525697229": [0], "1525756157": [0], "1525963260": [0], "1526134242": [0, 1], "1526178287": [0], "1526272037": [0], "1526311087": [0], "1526730044": [0], "1526745623": [0], "1526793325": [0], "1527140752": [0], "1527375236": [0], "1527696585": [0], "1527881500": [0], "1528200386": [0], "1528443821": [0], "1528731420": [0], "1529338428": [0], "1529356685": [0], "1529862537": [0], "1530936754": [0], "1531119040": [0], "1531167424": [0], "1531177594": [0], "1531269804": [0], "1531912840": [0], "1532058524": [0, 1], "1532543260": [0, 1], "1532675201": [0], "1533065875": [0], "1533273791": [0], "1533344601": [0], "1533554482": [0, 1], "1534047536": [0], "1534055398": [0], "1534453938": [0], "1534639294": [0], "1534687023": [0], "1534846888": [0], "1535024507": [0], "1535283449": [0], "1535662487": [0], "1536078160": [0], "1536243007": [0], "1536390477": [0], "1536426232": [0], "1536634822": [0], "1536765309": [0], "1537240075": [0], "1537272969": [0], "1537408786": [0], "1537600270": [0, 1], "1537613187": [0, 1], "1537763900": [0], "1538011412": [0], "1538253741": [0], "1538697143": [0], "1538744944": [0], "1538958511": [0], "1539165146": [0], "1539293356": [0], "1539297990": [0], "1539387665": [0], "1539469761": [0], "1539719546": [0], "1539763817": [0], "1539950474": [0], "1539996128": [0], "1540130569": [0], "1540470495": [0], "1540581750": [0], "1540707897": [0], "1540865225": [0], "1541030978": [0], "1541346411": [0], "1541527470": [0], "1542146611": [0], "1542330910": [0], "1542339218": [0], "1542409231": [0], "1542460503": [0], "1542547552": [0], "1542558682": [0], "1542769316": [0], "1542802191": [0], "1542838132": [0], "1542908586": [0], "1543074914": [0], "1543387412": [0], "1543388050": [0], "1543629748": [0], "1544747792": [0, 1], "1544822957": [0], "1544835982": [0], "1545170420": [0], "1545326271": [0], "1545459306": [0], "1545487859": [0], "1545857279": [0], "1545980029": [0, 1], "1546220819": [0], "1546305573": [0], "1546391877": [0], "1546458893": [0], "1546522158": [0], "1546568150": [0], "1546715355": [0], "1547137898": [0], "1547219624": [0, 1], "1547767314": [0], "1547788660": [0], "1547826502": [0], "1548125400": [0], "1548561596": [0], "1548612262": [0], "1549218627": [0], "1549294072": [0], "1549477033": [0], "1549579701": [0], "1549633824": [0], "1549965511": [0], "1550234873": [0], "1550487679": [0], "1550497920": [0], "1550724519": [0], "1550738338": [0], "1550910638": [0], "1550939942": [0], "1551254401": [0], "1551575734": [0], "1551627694": [0], "1551883705": [0], "1552332688": [0], "1552446466": [0], "1553528594": [0], "1553584829": [0], "1553698015": [0], "1553764673": [0], "1554605554": [0], "1554700608": [0], "1555186994": [0], "1556089764": [0], "1556094963": [0], "1556185394": [0], "1556843311": [0], "1557034690": [0], "1557356280": [0], "1557385226": [0], "1557429225": [0], "1557532558": [0, 1], "1557790105": [0], "1557939516": [0, 1], "1558109051": [0], "1558204959": [0], "1558932070": [0, 1], "1559126508": [0], "1559301752": [0], "1559475599": [0], "1559735792": [0], "1559865853": [0, 1], "1560124024": [0, 1], "1560262981": [0], "1560438868": [0], "1560727252": [0], "1561578843": [0], "1561610362": [0], "1562024743": [0], "1562049440": [0], "1562122622": [0], "1562612767": [0], "1562692278": [0], "1562765783": [0], "1562992524": [0], "1563137908": [0], "1563747292": [0], "1563786401": [0], "1564406130": [0], "1564427592": [0], "1564584450": [0, 1], "1564842599": [0], "1564981711": [0], "1565725180": [0], "1566374565": [0], "1566535456": [0], "1566634479": [0], "1566772190": [0], "1566921678": [0], "1566938889": [0], "1567003733": [0], "1567086604": [0], "1567199620": [0], "1567428350": [0], "1567724715": [0], "1568069480": [0], "1568084827": [0], "1568132899": [0], "1568196654": [0], "1568242538": [0], "1568428219": [0], "1568580785": [0], "1568812908": [0], "1569069375": [0], "1569114937": [0, 1], "1569122239": [0], "1569137449": [0], "1569294742": [0], "1569364055": [0], "1569367466": [0], "1569786593": [0], "1569787270": [0], "1569904894": [0], "1569975459": [0], "1570578024": [0], "1570651310": [0], "1570804840": [0], "1571094845": [0], "1571217109": [0], "1571398393": [0], "1571426000": [0], "1571707723": [0], "1571752990": [0], "1572257005": [0], "1572347267": [0], "1572718095": [0], "1572825637": [0, 1], "1573116050": [0], "1573474354": [0], "1573593534": [0], "1573943334": [0], "1574124354": [0, 1], "1574340109": [0], "1574482868": [0], "1574635241": [0], "1574941480": [0], "1575180827": [0], "1575562948": [0], "1575633166": [0], "1575784338": [0], "1576342724": [0], "1576433278": [0], "1576468949": [0], "1576739365": [0], "1576820923": [0], "1576837936": [0], "1577494574": [0], "1577833516": [0], "1577894905": [0], "1578322293": [0], "1578635514": [0], "1578660093": [0], "1578682895": [0], "1578738874": [0], "1578768274": [0], "1578880081": [0], "1578952701": [0], "1579340503": [0], "1579376747": [0], "1579479361": [0], "1580039385": [0], "1580204395": [0], "1580216903": [0], "1580240766": [0], "1580364757": [0], "1580471005": [0], "1580503482": [0], "1580728054": [0], "1580960597": [0], "1581003453": [0], "1581067369": [0], "1581182521": [0], "1581227956": [0], "1581573162": [0], "1582038552": [0], "1582278802": [0], "1582289426": [0], "1582451156": [0], "1582523782": [0], "1582709623": [0], "1582830187": [0], "1582958992": [0], "1583142088": [0], "1583363841": [0], "1583592485": [0], "1583776000": [0], "1583800631": [0, 1], "1583808535": [0], "1584309970": [0], "1584350286": [0, 1], "1584553032": [0], "1584684307": [0], "1584889955": [0], "1585627295": [0], "1585761150": [0], "1586088299": [0], "1586237366": [0], "1586311627": [0], "1586365418": [0, 1], "1586619947": [0], "1587207463": [0], "1587224271": [0], "1587338140": [0, 1], "1587734313": [0], "1587880212": [0], "1588237935": [0], "1588277076": [0], "1588729288": [0], "1589207582": [0], "1589713279": [0], "1590237151": [0], "1590489688": [0], "1590693336": [0], "1590921683": [0], "1591562013": [0], "1591720914": [0], "1591955819": [0], "1592341989": [0], "1592403800": [0], "1592740731": [0], "1592910952": [0], "1592993013": [0], "1593080757": [0], "1593287769": [0], "1593471488": [0], "1593787430": [0], "1593856478": [0], "1593858046": [0], "1594384658": [0], "1594587956": [0], "1594622606": [0], "1594643960": [0], "1594756829": [0], "1594780751": [0], "1595012885": [0], "1595087871": [0], "1595905902": [0], "1596009073": [0], "1596108946": [0], "1596456989": [0], "1596518317": [0], "1596597145": [0, 1], "1596719329": [0, 1], "1596841303": [0], "1596870543": [0], "1596970674": [0], "1597506300": [0], "1597514861": [0], "1597730213": [0], "1597870481": [0], "1597930580": [0], "1598287092": [0], "1598349500": [0], "1598814606": [0], "1598993639": [0], "1599110345": [0], "1599144342": [0], "1599561823": [0], "1599615939": [0], "1599769963": [0], "1599787392": [0], "1599825390": [0], "1599901031": [0], "1600032837": [0], "1600471783": [0], "1600512656": [0], "1600532558": [0], "1601273982": [0], "1601423283": [0], "1601728271": [0], "1602003928": [0], "1602082003": [0], "1602548002": [0], "1603230007": [0], "1604011504": [0, 1], "1605015185": [0], "1605241084": [0], "1605474584": [0], "1605590241": [0], "1605643913": [0], "1605650247": [0], "1605977383": [0], "1605990461": [0], "1605993063": [0], "1606119864": [0], "1606348967": [0], "1606648311": [0], "1606933433": [0], "1607192566": [0, 1], "1607968663": [0], "1608252933": [0], "1608270169": [0], "1608354593": [0], "1608760010": [0, 1], "1608784841": [0], "1608964051": [0], "1608972852": [0], "1609134808": [0], "1609329981": [0, 1], "1609576730": [0], "1609796317": [0], "1610102124": [0], "1610372821": [0], "1610472402": [0], "1610783680": [0], "1610924805": [0], "1611202225": [0], "1611414471": [0, 1], "1611638409": [0], "1611851839": [0], "1612403575": [0], "1612683383": [0], "1612948838": [0], "1613154021": [0], "1613156654": [0], "1613542815": [0], "1613835181": [0], "1614571792": [0], "1614585450": [0], "1614610463": [0], "1614969659": [0], "1615008975": [0], "1615350500": [0], "1615433580": [0], "1615530696": [0], "1615694398": [0, 1], "1616120597": [0], "1616452278": [0], "1616479462": [0], "1616480280": [0], "1616519835": [0], "1616618046": [0], "1616649090": [0], "1616766718": [0], "1616972609": [0], "1617096660": [0], "1617111440": [0], "1617369998": [0], "1617374863": [0], "1617494640": [0], "1617506937": [0], "1617591619": [0], "1617757908": [0], "1617918152": [0], "1617963346": [0], "1617979203": [0], "1618062965": [0], "1618272176": [0], "1618738379": [0], "1618813313": [0], "1619267426": [0], "1619281020": [0], "1619438798": [0], "1620799224": [0], "1621471189": [0], "1622392243": [0], "1622760877": [0], "1622844933": [0, 1], "1622962377": [0], "1622962452": [0], "1623053817": [0], "1623087792": [0], "1623136315": [0], "1623877968": [0], "1623919427": [0], "1624236094": [0], "1624441276": [0], "1625640859": [0], "1625770912": [0], "1625974979": [0], "1626042347": [0], "1626197013": [0], "1626661735": [0], "1626960470": [0], "1627649797": [0], "1627995086": [0, 1], "1628357611": [0], "1628360181": [0], "1628603513": [0], "1628635445": [0], "1628748279": [0], "1628932686": [0], "1629276759": [0], "1629436687": [0], "1629797782": [0], "1630013393": [0], "1630991441": [0], "1631007967": [0], "1631596332": [0], "1632043984": [0], "1632071685": [0], "1632198661": [0], "1632257198": [0], "1632271101": [0], "1632474922": [0], "1632480735": [0], "1632488243": [0], "1632652052": [0], "1632956530": [0], "1633228833": [0], "1633427679": [0], "1633436892": [0], "1633612448": [0], "1633998042": [0], "1634194723": [0], "1634634324": [0], "1635124580": [0], "1635145886": [0], "1635205914": [0, 1], "1635384589": [0], "1635586999": [0], "1635800404": [0], "1635848327": [0], "1635985772": [0], "1636030207": [0], "1636203537": [0], "1636571388": [0], "1636579337": [0], "1636603901": [0, 1], "1636612660": [0], "1636677731": [0], "1636936782": [0], "1637016912": [0, 1], "1637024824": [0], "1637032277": [0], "1637062488": [0], "1637619765": [0], "1637771713": [0], "1638644157": [0], "1639124274": [0], "1639810266": [0], "1640048436": [0], "1640095752": [0], "1640159149": [0], "1640253378": [0, 1], "1640269333": [0], "1640313333": [0], "1640539763": [0], "1640581956": [0], "1640816465": [0], "1640916422": [0], "1640969116": [0], "1641054956": [0], "1641155606": [0], "1641182848": [0], "1641390972": [0], "1641578907": [0], "1641658955": [0], "1641757480": [0], "1641881177": [0], "1641897947": [0], "1642286282": [0], "1642302976": [0, 1], "1642545388": [0], "1643397908": [0], "1643935728": [0, 1], "1644346564": [0], "1644415336": [0], "1644621450": [0], "1644976119": [0], "1645251318": [0], "1645470411": [0, 1], "1646213900": [0], "1646287105": [0], "1646296701": [0], "1646323650": [0], "1646328731": [0], "1646560068": [0], "1646996033": [0], "1647420663": [0], "1648035341": [0], "1648467145": [0], "1648539287": [0], "1648728581": [0], "1649368657": [0], "1649809451": [0], "1650002700": [0], "1650069330": [0], "1650267810": [0], "1650413398": [0, 1], "1650710167": [0], "1651077498": [0], "1651287333": [0], "1651348507": [0], "1651438940": [0], "1652165383": [0], "1652379819": [0], "1652393760": [0], "1652837865": [0], "1653505430": [0], "1653642725": [0], "1653898623": [0], "1654067888": [0], "1654239565": [0], "1654280291": [0], "1654345025": [0], "1654910965": [0], "1654922831": [0], "1655036034": [0], "1655163327": [0], "1655220251": [0], "1655418608": [0, 1], "1655453481": [0], "1655840935": [0], "1655949550": [0], "1656053711": [0], "1656072681": [0], "1656224214": [0], "1656465818": [0], "1656642463": [0], "1656817001": [0], "1656933366": [0], "1657247298": [0], "1657332896": [0], "1657611818": [0, 1], "1657873070": [0], "1658214761": [0], "1658245849": [0], "1658505725": [0], "1658522194": [0], "1658528203": [0], "1658867623": [0], "1658941803": [0], "1658950775": [0], "1659106401": [0], "1659328462": [0], "1659801715": [0, 1], "1660165686": [0], "1660296130": [0], "1660589898": [0], "1660761125": [0], "1660961870": [0], "1661288016": [0], "1662106377": [0], "1662162198": [0], "1662697164": [0], "1662890252": [0], "1663030910": [0], "1663206178": [0], "1663209979": [0], "1663229925": [0], "1663231772": [0], "1663272805": [0], "1663488762": [0], "1663820689": [0], "1663866259": [0], "1663946193": [0], "1664033155": [0], "1664054639": [0], "1664065901": [0, 1], "1664147831": [0], "1664194915": [0], "1664276479": [0], "1664277338": [0], "1664331345": [0], "1664463933": [0], "1664505556": [0], "1664718301": [0], "1664801374": [0], "1664808290": [0], "1664829331": [0], "1664987004": [0], "1665190683": [0], "1665240758": [0], "1665269842": [0], "1665840831": [0, 1], "1666099072": [0], "1666204459": [0], "1666449325": [0], "1666630749": [0], "1666762742": [0], "1667329304": [0], "1667454522": [0], "1668040156": [0], "1668186430": [0], "1668821236": [0], "1668959019": [0], "1669055731": [0], "1669586553": [0], "1669836514": [0], "1669993050": [0], "1670484321": [0], "1670525054": [0], "1670813952": [0], "1670829024": [0], "1671501530": [0], "1671752914": [0], "1671788902": [0], "1672012437": [0], "1672030303": [0], "1672096183": [0], "1672116013": [0], "1672257460": [0], "1672416009": [0], "1672511191": [0], "1672916790": [0], "1673336550": [0], "1673526009": [0], "1673596654": [0], "1673830312": [0], "1674037817": [0], "1674071203": [0], "1674152692": [0], "1675138700": [0], "1675197253": [0], "1675229624": [0], "1675450795": [0], "1675622990": [0], "1675745141": [0], "1675862786": [0], "1675890948": [0], "1676225281": [0], "1676273826": [0], "1676282120": [0], "1676517097": [0], "1676774520": [0], "1676815649": [0], "1677327214": [0], "1677372673": [0], "1677528816": [0], "1677693460": [0], "1678015857": [0, 1], "1678478701": [0], "1678489310": [0], "1678810247": [0], "1678961358": [0], "1679013008": [0], "1679050399": [0], "1679074150": [0], "1679210352": [0], "1679489402": [0], "1679514526": [0], "1679528479": [0], "1680020425": [0], "1680259988": [0, 1], "1680378071": [0], "1680659485": [0], "1680698976": [0], "1680943683": [0], "1681233801": [0], "1681273088": [0], "1681466067": [0], "1681859809": [0], "1682071021": [0], "1682117693": [0, 1], "1682420592": [0], "1682520965": [0], "1682741657": [0], "1683215211": [0], "1683277410": [0], "1683367509": [0, 1], "1683585978": [0, 1], "1684122282": [0], "1684123010": [0], "1684374172": [0], "1684378675": [0], "1684646587": [0], "1684803438": [0], "1685318287": [0], "1685497823": [0], "1685520938": [0], "1686137134": [0], "1686222438": [0], "1686488545": [0], "1686518273": [0], "1686730885": [0], "1686976498": [0], "1687268889": [0], "1687381149": [0], "1687422079": [0], "1687648881": [0], "1687730983": [0, 1], "1688477838": [0], "1688631217": [0], "1688650486": [0, 1], "1688813532": [0], "1688962695": [0], "1689025166": [0, 1], "1689375621": [0], "1689491406": [0], "1689788130": [0], "1689941328": [0], "1690382155": [0], "1690821709": [0], "1691078146": [0], "1691481375": [0], "1691544728": [0], "1691629702": [0], "1692248795": [0], "1692624338": [0], "1692687182": [0], "1692735711": [0], "1692759442": [0, 1], "1693059473": [0], "1693064550": [0], "1693189577": [0], "1693332900": [0], "1693762207": [0], "1693819107": [0], "1694050162": [0], "1694592591": [0], "1694606033": [0], "1694767042": [0], "1694853076": [0], "1694929539": [0], "1695015789": [0], "1695210325": [0], "1695253681": [0], "1695501639": [0], "1695893116": [0], "1696008122": [0], "1696074296": [0], "1696439012": [0], "1696991399": [0], "1697202594": [0], "1697211090": [0], "1697324631": [0], "1697889100": [0], "1698062530": [0], "1699166023": [0], "1699552203": [0], "1699635309": [0], "1700031601": [0], "1700393261": [0], "1700540017": [0], "1700878032": [0], "1701065518": [0], "1701200648": [0, 1], "1701405727": [0], "1701560923": [0], "1701606553": [0], "1701902363": [0], "1701920727": [0], "1701997309": [0], "1702018576": [0, 1], "1702342632": [0], "1702352657": [0], "1702372588": [0], "1702849590": [0], "1702885552": [0], "1703424844": [0], "1704152984": [0], "1704176676": [0], "1704242582": [0], "1704816425": [0], "1705031860": [0], "1705508480": [0], "1705543850": [0], "1705816789": [0], "1705844512": [0], "1706081643": [0], "1706134855": [0], "1706304174": [0], "1707013987": [0], "1707059973": [0], "1707104400": [0], "1707426564": [0], "1707847236": [0], "1707977357": [0], "1708258559": [0], "1708447697": [0], "1708896700": [0], "1708934351": [0], "1709207408": [0], "1709393721": [0], "1709469488": [0], "1709587543": [0], "1710026127": [0], "1710192619": [0], "1710218042": [0], "1710776394": [0], "1710838695": [0], "1711036496": [0], "1711267562": [0], "1711299746": [0], "1711400498": [0], "1711425741": [0], "1712448303": [0], "1712775610": [0], "1712971214": [0], "1713187978": [0], "1713196424": [0], "1714134679": [0], "1714820329": [0], "1714855558": [0], "1715002771": [0], "1715084126": [0], "1715492238": [0], "1715609534": [0], "1715965612": [0], "1716024929": [0], "1716160272": [0], "1716293164": [0], "1716385849": [0], "1716553098": [0], "1716723506": [0], "1717282078": [0], "1717292778": [0, 1], "1717410944": [0], "1717487957": [0, 1], "1717632238": [0], "1718148569": [0], "1718181638": [0], "1718427311": [0], "1718498639": [0], "1718533342": [0], "1718650043": [0], "1718706444": [0], "1719045150": [0], "1719071905": [0], "1719185824": [0], "1719363303": [0], "1719634414": [0, 1], "1719797414": [0], "1719917309": [0], "1720300394": [0], "1720461353": [0], "1720510984": [0], "1720612324": [0], "1720893971": [0], "1721576278": [0], "1721722858": [0], "1721897928": [0], "1722668240": [0], "1723695761": [0], "1723757632": [0], "1723857900": [0], "1723862033": [0, 1], "1723945008": [0], "1723966189": [0], "1724653465": [0], "1724929376": [0], "1725039356": [0], "1725154496": [0], "1725174637": [0], "1725281221": [0], "1725308159": [0, 1], "1725972896": [0], "1726416641": [0], "1726687429": [0], "1726689998": [0], "1727024638": [0], "1727086552": [0], "1727543607": [0], "1727558497": [0], "1727622355": [0], "1728047531": [0], "1728754346": [0], "1728976946": [0], "1729044458": [0], "1729089308": [0], "1729432851": [0], "1729716576": [0], "1729816182": [0], "1729912953": [0], "1730154135": [0], "1730231863": [0], "1730442176": [0], "1731062324": [0], "1731422342": [0], "1731846800": [0], "1731905106": [0], "1732071077": [0], "1732277362": [0], "1732292031": [0], "1733577299": [0], "1733665306": [0], "1733667386": [0], "1733836452": [0], "1733896546": [0], "1734056220": [0, 1], "1734152403": [0], "1734396171": [0], "1734656638": [0], "1734679117": [0, 1], "1735319725": [0], "1735445928": [0, 1], "1735465139": [0], "1736092353": [0, 1], "1736094891": [0], "1736721858": [0], "1737214255": [0], "1737607648": [0], "1738103801": [0], "1738283938": [0], "1738380456": [0, 1], "1738428152": [0], "1738754131": [0], "1738877796": [0], "1738883940": [0], "1738931586": [0], "1739490913": [0], "1739784569": [0], "1739936483": [0], "1739949803": [0], "1740407519": [0], "1740449786": [0], "1740736893": [0], "1740819964": [0], "1741015395": [0], "1741250686": [0], "1741581878": [0], "1741600601": [0], "1741777657": [0], "1741889248": [0], "1742522231": [0], "1743458008": [0], "1743875565": [0], "1744174674": [0], "1744328030": [0, 1], "1744407893": [0], "1744444882": [0], "1744844625": [0], "1745245252": [0], "1745552291": [0], "1746064396": [0], "1746246022": [0], "1746325312": [0], "1746391501": [0], "1746441439": [0], "1746785285": [0], "1746880793": [0], "1746964527": [0], "1747056988": [0], "1747494645": [0], "1747514711": [0], "1747533492": [0], "1747681969": [0], "1747689393": [0], "1747790191": [0], "1747968734": [0], "1748084542": [0], "1748324833": [0], "1748332144": [0], "1748380748": [0], "1748662014": [0], "1749009204": [0, 1], "1749107546": [0], "1749735347": [0], "1750493903": [0], "1750977140": [0], "1751227376": [0], "1751611402": [0], "1751715236": [0], "1751717848": [0], "1752215989": [0], "1752221702": [0, 1], "1752222119": [0], "1752492766": [0], "1752514518": [0, 1], "1752944449": [0], "1752969900": [0], "1753542028": [0], "1753586272": [0], "1753655042": [0], "1753892160": [0], "1754714651": [0], "1755001166": [0], "1755159859": [0], "1755291228": [0], "1756062485": [0], "1756139114": [0], "1756364862": [0], "1756484997": [0], "1756656174": [0], "1756780424": [0], "1756917452": [0], "1757190879": [0], "1757256593": [0], "1757504492": [0], "1757667086": [0], "1758068194": [0], "1758346414": [0], "1758436275": [0], "1758554914": [0], "1758622124": [0], "1758755477": [0], "1759427058": [0], "1759862613": [0], "1759872001": [0], "1759946214": [0], "1760924659": [0], "1761008594": [0], "1761329078": [0], "1761363485": [0], "1761373862": [0], "1761456132": [0], "1761462155": [0], "1761541382": [0], "1761972787": [0], "1762581183": [0], "1762918674": [0], "1763086682": [0], "1763261983": [0], "1763452674": [0], "1763732533": [0], "1763913584": [0], "1764221330": [0], "1764484226": [0], "1764517032": [0], "1764524536": [0], "1765155405": [0], "1765359288": [0], "1766106313": [0], "1766925262": [0], "1766992792": [0], "1767274522": [0], "1768072088": [0], "1768506480": [0], "1769167116": [0], "1769827672": [0], "1769944236": [0], "1770275419": [0], "1770774028": [0], "1771041018": [0], "1771122372": [0], "1771285457": [0], "1771525575": [0], "1771758562": [0], "1772547794": [0], "1772907004": [0], "1772908696": [0], "1773088548": [0], "1773820048": [0], "1773919264": [0], "1774118949": [0], "1774713806": [0], "1774940882": [0], "1775598185": [0], "1775702373": [0], "1775876110": [0, 1], "1776223306": [0, 1], "1776629676": [0], "1776675330": [0], "1776815902": [0], "1776887447": [0], "1777079341": [0], "1777392053": [0], "1777762309": [0], "1778389685": [0], "1778662305": [0], "1779565450": [0], "1779593738": [0], "1780269879": [0], "1780274310": [0], "1780339039": [0], "1780341787": [0], "1780598704": [0], "1780947945": [0], "1781504891": [0, 1], "1781816067": [0], "1782286169": [0], "1782287323": [0], "1782369276": [0], "1782485357": [0], "1782818226": [0], "1783347582": [0], "1783414174": [0], "1783991019": [0], "1784405532": [0], "1785154000": [0], "1785253499": [0], "1785381581": [0], "1785764824": [0], "1786507847": [0], "1786902606": [0], "1786914149": [0], "1787123985": [0], "1787407239": [0], "1787467914": [0], "1787582684": [0], "1788104806": [0], "1788105487": [0], "1788512525": [0], "1788560986": [0], "1789019454": [0], "1789063885": [0], "1789378194": [0], "1789988809": [0], "1790094463": [0], "1790254054": [0, 1], "1790376634": [0], "1790764512": [0], "1791028545": [0], "1791553962": [0], "1791600275": [0], "1791823620": [0], "1791829972": [0], "1792570336": [0], "1792602966": [0], "1792720501": [0], "1792918250": [0], "1793142237": [0], "1793292474": [0], "1793752405": [0], "1793848687": [0], "1793929350": [0], "1794084601": [0], "1794292631": [0], "1794671340": [0], "1794806550": [0], "1795064249": [0], "1795209405": [0], "1795241251": [0], "1795310433": [0], "1795342334": [0], "1795342917": [0], "1795470189": [0], "1795521826": [0], "1795764300": [0], "1795795566": [0], "1795978428": [0], "1796352119": [0], "1796508811": [0], "1797069813": [0], "1797134985": [0], "1797463701": [0], "1797533334": [0], "1797816470": [0], "1797928883": [0], "1798531532": [0], "1798989414": [0], "1799141180": [0], "1799702576": [0], "1799829922": [0], "1799981543": [0], "1800043061": [0], "1800155205": [0], "1800328419": [0], "1800337381": [0], "1800417747": [0], "1800694675": [0], "1801082264": [0], "1801518843": [0], "1801864646": [0], "1801895083": [0], "1801932981": [0], "1803007960": [0], "1803041544": [0], "1803322128": [0], "1803440829": [0], "1803880133": [0], "1804179392": [0], "1804350645": [0], "1804367161": [0], "1804387177": [0], "1804453075": [0], "1804843537": [0], "1804935047": [0], "1805035661": [0], "1805137116": [0, 1], "1805247837": [0], "1805454425": [0], "1806163089": [0], "1806650573": [0], "1806729984": [0], "1806881740": [0], "1806940548": [0], "1807010711": [0], "1807111796": [0], "1807352586": [0], "1807527730": [0, 1], "1807881556": [0], "1807884635": [0], "1808309413": [0], "1808905109": [0], "1809175057": [0], "1809384803": [0], "1809720858": [0], "1809996364": [0], "1810174470": [0], "1810553381": [0], "1810704724": [0], "1810907373": [0], "1810909527": [0], "1810919765": [0], "1811270909": [0], "1811431578": [0], "1811444913": [0], "1811458639": [0], "1811526503": [0], "1811713713": [0], "1812327250": [0], "1812421503": [0], "1812498567": [0], "1812813179": [0], "1812907230": [0], "1812916096": [0], "1813196976": [0], "1813325265": [0], "1813517508": [0], "1813530941": [0], "1813596016": [0], "1814109099": [0], "1814404718": [0], "1815151199": [0], "1815504444": [0], "1816371493": [0], "1816546664": [0], "1816820313": [0], "1817310918": [0], "1817680667": [0], "1817712998": [0], "1817826785": [0], "1817847663": [0], "1817889863": [0, 1], "1818013177": [0], "1818025887": [0], "1818608309": [0], "1818857566": [0], "1819013194": [0], "1819166510": [0], "1819271614": [0], "1819505536": [0], "1819644653": [0], "1819734107": [0], "1819960385": [0], "1820163872": [0], "1820258263": [0], "1820535104": [0], "1820885767": [0], "1820910259": [0], "1820911993": [0], "1821311704": [0], "1821517114": [0], "1821519074": [0], "1821674275": [0], "1821999828": [0], "1822244292": [0, 1], "1822308216": [0], "1822453611": [0], "1822480210": [0], "1822914161": [0], "1823095573": [0], "1823099369": [0], "1823508446": [0, 1], "1823827811": [0], "1824548981": [0], "1824789124": [0], "1825202484": [0], "1825600443": [0], "1825737730": [0], "1825838578": [0], "1826085108": [0], "1826847066": [0], "1827328310": [0], "1827692941": [0], "1828357431": [0], "1828421908": [0], "1828517285": [0], "1828537450": [0], "1828826047": [0], "1829009037": [0], "1829469515": [0], "1829974411": [0], "1830017527": [0], "1830028343": [0], "1830044507": [0], "1830567868": [0], "1830779764": [0], "1831538515": [0, 1], "1831742559": [0], "1831752370": [0], "1831783465": [0], "1831832938": [0], "1831890625": [0], "1832017663": [0], "1832070276": [0], "1832573478": [0], "1832758468": [0], "1833674961": [0], "1833705541": [0], "1833855691": [0], "1834293370": [0], "1834940579": [0], "1834968638": [0], "1835376763": [0], "1835510995": [0], "1835723552": [0], "1836165127": [0], "1837472007": [0], "1837608946": [0], "1837625021": [0], "1837801619": [0], "1837951786": [0], "1837955864": [0, 1], "1838085783": [0], "1838387899": [0], "1838429854": [0], "1838702732": [0], "1838742232": [0], "1838860555": [0], "1839104169": [0], "1839195731": [0], "1839282940": [0], "1839819214": [0], "1839984914": [0], "1839992955": [0], "1839997013": [0], "1840123962": [0], "1840309478": [0], "1840523874": [0], "1841322378": [0], "1841582790": [0], "1842287335": [0], "1842843229": [0], "1843171728": [0, 1], "1843301262": [0], "1843468636": [0], "1843702122": [0, 1], "1843988727": [0], "1844227707": [0], "1844668995": [0], "1844975870": [0], "1845197377": [0], "1845312602": [0], "1845519498": [0], "1846582978": [0], "1846855164": [0], "1847438355": [0], "1847439803": [0, 1], "1847463735": [0], "1847568502": [0], "1848318570": [0, 1], "1848532968": [0], "1848632297": [0, 1], "1849021819": [0], "1849237128": [0], "1849687490": [0], "1849944329": [0], "1850132301": [0], "1850142791": [0], "1850278033": [0], "1850349411": [0], "1850380789": [0], "1850398788": [0], "1850543038": [0], "1850644940": [0], "1850819648": [0], "1850875364": [0], "1851041264": [0, 1], "1851152049": [0], "1851648624": [0], "1852136086": [0], "1852328210": [0], "1853283411": [0], "1853425525": [0], "1853652574": [0], "1853917983": [0], "1853972049": [0], "1853992742": [0, 1], "1854190642": [0, 1], "1854654733": [0], "1854678448": [0], "1854877152": [0], "1854925181": [0], "1855002542": [0], "1855167181": [0], "1855176766": [0], "1855232352": [0], "1855491287": [0], "1855712652": [0], "1855794768": [0], "1856082124": [0], "1856575365": [0], "1857131716": [0], "1857898516": [0], "1858890768": [0], "1859021291": [0], "1859206372": [0, 1], "1859206741": [0], "1859657734": [0], "1859669222": [0], "1859690364": [0], "1859728327": [0], "1859806846": [0], "1859922579": [0], "1860037674": [0], "1860245815": [0], "1861306418": [0], "1861344058": [0], "1861403447": [0], "1861423189": [0], "1861482702": [0], "1862126514": [0], "1862242142": [0], "1863100208": [0, 1], "1863126005": [0], "1863178568": [0], "1863541445": [0], "1864124312": [0], "1864353895": [0], "1864449678": [0], "1864696756": [0], "1864882929": [0], "1864944543": [0], "1865035567": [0], "1865263623": [0], "1865341132": [0], "1865440885": [0], "1865583316": [0], "1865872327": [0], "1865912543": [0], "1866046607": [0], "1866435621": [0], "1866470807": [0], "1866625012": [0], "1866915954": [0], "1866981435": [0], "1867035889": [0], "1867820805": [0], "1867982776": [0], "1868107502": [0], "1868141654": [0], "1868216273": [0], "1868271203": [0], "1868542116": [0], "1868660434": [0], "1869050273": [0], "1869560055": [0], "1869616840": [0], "1870174594": [0], "1870376576": [0], "1870577358": [0], "1870971436": [0, 1], "1871318309": [0], "1871708927": [0], "1871712305": [0], "1871865630": [0], "1872111504": [0], "1872170813": [0], "1872249309": [0], "1872460618": [0], "1872872117": [0], "1872967668": [0], "1873663668": [0], "1873946173": [0], "1874438205": [0], "1874572710": [0], "1874639624": [0], "1875117790": [0], "1875922975": [0], "1875977923": [0], "1875987277": [0], "1876430383": [0], "1876474801": [0], "1876651253": [0], "1876935347": [0], "1877074863": [0], "1877098703": [0], "1877242042": [0], "1877312067": [0], "1877820210": [0], "1878026601": [0], "1878223995": [0], "1878245214": [0], "1878308377": [0], "1878585353": [0], "1878704819": [0], "1878862982": [0], "1879312155": [0], "1879810994": [0], "1880329660": [0], "1880417657": [0], "1880441026": [0], "1880833647": [0], "1881200661": [0], "1881391850": [0], "1881434811": [0], "1881574209": [0], "1882301749": [0], "1882337187": [0], "1882381534": [0], "1882477972": [0], "1882782519": [0], "1883605821": [0], "1884434297": [0], "1884718414": [0], "1885124285": [0], "1885192374": [0], "1885417446": [0], "1885472453": [0], "1885595326": [0], "1885984120": [0], "1886141639": [0], "1886296990": [0], "1886757366": [0], "1887216626": [0], "1887578819": [0], "1887581632": [0, 1], "1887917765": [0], "1888181600": [0], "1888300566": [0], "1888508900": [0], "1888971846": [0], "1889174249": [0], "1889268029": [0], "1889799142": [0], "1889859633": [0], "1889883079": [0], "1890735720": [0], "1890766544": [0], "1890794296": [0], "1891018075": [0], "1891162542": [0], "1891528212": [0, 1], "1891567027": [0], "1891961644": [0], "1892438257": [0], "1892488303": [0], "1892826633": [0], "1893569704": [0, 1], "1893888919": [0], "1894050667": [0], "1894283431": [0], "1894528806": [0], "1895261370": [0, 1], "1895410982": [0, 1], "1895496134": [0], "1895652587": [0], "1896335746": [0], "1896636172": [0], "1896872122": [0], "1897467280": [0], "1897879071": [0], "1898114220": [0], "1898552980": [0], "1898579158": [0], "1898586169": [0], "1898630004": [0], "1898833115": [0], "1899084445": [0, 1], "1899623481": [0], "1899815354": [0], "1899912111": [0], "1900475245": [0], "1900892501": [0], "1900930139": [0], "1901032412": [0], "1901303421": [0], "1901527505": [0], "1901630498": [0], "1901738978": [0], "1902190333": [0], "1902720787": [0], "1902728284": [0], "1902787166": [0], "1902928937": [0], "1902935782": [0], "1903031574": [0], "1903126083": [0], "1903602114": [0], "1904284092": [0], "1904294639": [0], "1904575064": [0], "1904673914": [0], "1904704993": [0], "1905962527": [0], "1906211793": [0], "1906632457": [0], "1906978760": [0], "1906980620": [0], "1907199950": [0], "1907200634": [0], "1907244129": [0], "1907600403": [0], "1907795862": [0], "1908015626": [0], "1908193722": [0], "1909100410": [0], "1909195317": [0], "1909250327": [0], "1909747727": [0], "1909952618": [0], "1910285002": [0], "1910304721": [0], "1910333205": [0], "1910465528": [0], "1910555294": [0], "1910973663": [0], "1911349704": [0], "1911620076": [0], "1911782154": [0], "1911809207": [0], "1911824416": [0], "1911882295": [0], "1912054953": [0], "1912366861": [0, 1], "1912689877": [0], "1913045721": [0], "1913155315": [0], "1913319150": [0], "1913730260": [0, 1], "1913774240": [0], "1914054115": [0], "1914111678": [0], "1914230784": [0], "1914251269": [0], "1914582472": [0], "1914705874": [0, 1], "1914903866": [0], "1914933188": [0], "1914989948": [0], "1914994275": [0], "1915156405": [0], "1915237506": [0], "1915245524": [0], "1915460847": [0], "1915536875": [0], "1915812771": [0], "1915872359": [0], "1916368719": [0], "1916766608": [0], "1916917030": [0], "1916986654": [0], "1916993320": [0], "1917187392": [0], "1917241679": [0], "1918212942": [0], "1918234601": [0], "1918661481": [0], "1918692373": [0], "1919433217": [0], "1919454382": [0], "1919548555": [0], "1919651107": [0], "1919683955": [0], "1919874619": [0], "1919942355": [0], "1920112893": [0], "1921860149": [0], "1921919409": [0], "1922197508": [0], "1922337264": [0], "1922432785": [0], "1922462020": [0], "1922486003": [0], "1922685054": [0], "1922702547": [0], "1922776493": [0], "1922898053": [0], "1923669925": [0], "1923685616": [0, 1], "1924261266": [0], "1924503410": [0], "1924719260": [0], "1925027867": [0], "1925175461": [0], "1925362126": [0], "1925461092": [0], "1925776797": [0], "1926096432": [0], "1926297786": [0], "1926536126": [0], "1926750247": [0], "1926836960": [0], "1926927540": [0], "1927031036": [0], "1927250571": [0], "1927748266": [0], "1927759195": [0], "1927949588": [0], "1927979453": [0], "1928001994": [0], "1928022212": [0], "1928153079": [0], "1928281944": [0, 1], "1928397055": [0], "1928459036": [0], "1928532386": [0], "1928907692": [0], "1929005214": [0], "1929339784": [0], "1929536304": [0], "1929568432": [0], "1929601724": [0], "1929753719": [0], "1929993366": [0], "1929997566": [0], "1930090313": [0], "1930330549": [0], "1930487638": [0], "1930504339": [0, 1], "1930527196": [0], "1931136621": [0], "1931165009": [0], "1931275981": [0], "1931539028": [0], "1931775705": [0], "1932150062": [0], "1932186329": [0], "1932243754": [0], "1932304419": [0], "1933155398": [0], "1933198062": [0], "1933471230": [0], "1934055585": [0], "1934253270": [0], "1934271684": [0], "1934410674": [0], "1934520286": [0], "1934555179": [0], "1935303856": [0], "1935423507": [0], "1935483025": [0], "1935512145": [0], "1935902953": [0], "1936352948": [0], "1936378211": [0], "1936804069": [0], "1936819527": [0], "1937102501": [0], "1937445760": [0], "1937691241": [0], "1938129385": [0], "1938493091": [0], "1938588210": [0], "1939290041": [0], "1939387001": [0], "1939636725": [0], "1940223523": [0], "1940348811": [0], "1940636857": [0], "1940813777": [0], "1941109529": [0], "1941355843": [0], "1941700070": [0], "1941785233": [0], "1941897443": [0], "1941929528": [0], "1942005672": [0], "1942042506": [0], "1942260317": [0], "1942446318": [0], "1942538685": [0], "1942590466": [0], "1942663631": [0], "1942775928": [0], "1943206779": [0], "1943688278": [0], "1943854877": [0], "1943942534": [0], "1944093926": [0], "1945271552": [0], "1945724801": [0], "1945966701": [0], "1946054285": [0], "1946104206": [0], "1946780217": [0], "1947180804": [0], "1947350735": [0], "1947515506": [0, 1], "1947715197": [0], "1948105551": [0], "1948376807": [0], "1949177264": [0], "1949192711": [0], "1949218838": [0], "1949963862": [0], "1950100333": [0], "1950128808": [0], "1950337974": [0], "1950543678": [0], "1950744916": [0], "1950847330": [0], "1950889758": [0], "1951453743": [0], "1951465527": [0], "1951769754": [0], "1951780490": [0], "1951821601": [0], "1951976634": [0], "1952186043": [0], "1952617025": [0], "1952880030": [0], "1953087407": [0, 1], "1953220136": [0], "1953436805": [0], "1953943712": [0], "1954016934": [0], "1954143470": [0], "1954265648": [0], "1954314780": [0], "1954749599": [0], "1955077113": [0], "1955154854": [0], "1955225227": [0], "1955593895": [0], "1955697808": [0], "1955867187": [0, 1], "1956167950": [0], "1956518673": [0], "1956664602": [0], "1957139536": [0], "1957355332": [0], "1957496090": [0], "1957986272": [0], "1958200263": [0], "1958479481": [0], "1958753419": [0], "1958773967": [0], "1958841703": [0], "1959185600": [0], "1959264281": [0], "1959272730": [0], "1959389261": [0], "1959620392": [0], "1959642628": [0], "1960164799": [0], "1960523633": [0], "1960546400": [0], "1960929070": [0], "1961173593": [0], "1961522515": [0], "1962033851": [0], "1963024207": [0], "1963281557": [0], "1963658469": [0], "1964047256": [0], "1964588242": [0], "1964599615": [0], "1964635412": [0], "1964657937": [0], "1964678798": [0], "1964911747": [0], "1965013480": [0], "1965063600": [0], "1965372806": [0], "1965395131": [0], "1965905128": [0], "1965918053": [0], "1965957531": [0], "1965996807": [0, 1], "1966504882": [0], "1966574246": [0], "1966575848": [0, 1], "1966754340": [0], "1966894640": [0], "1967423479": [0], "1967739955": [0], "1968137307": [0], "1968225664": [0], "1968315795": [0], "1968695515": [0], "1968813565": [0], "1969460068": [0], "1969896422": [0], "1970115987": [0], "1970309118": [0], "1970637412": [0], "1970692238": [0], "1970718898": [0], "1971132001": [0], "1971263072": [0, 1], "1971285168": [0], "1971386675": [0], "1971487857": [0], "1973726340": [0], "1974007109": [0], "1974033734": [0], "1974389172": [0], "1974509991": [0], "1974929919": [0], "1975198310": [0], "1975453729": [0], "1975686691": [0], "1975894087": [0], "1975914322": [0], "1975937519": [0], "1975966684": [0], "1976128348": [0], "1976375567": [0], "1976418187": [0], "1976466985": [0], "1977308476": [0], "1977380022": [0], "1977381543": [0], "1977478004": [0], "1977509999": [0], "1977778551": [0], "1977792022": [0], "1977879257": [0], "1978175315": [0], "1978237481": [0], "1978244204": [0], "1978444864": [0], "1978904010": [0], "1979571963": [0], "1980370344": [0], "1980385128": [0], "1980761147": [0], "1980836161": [0], "1981018246": [0], "1981337596": [0], "1981362630": [0], "1981675377": [0], "1981835365": [0], "1981929223": [0], "1982077123": [0], "1982287769": [0], "1982416755": [0], "1982493659": [0], "1982634101": [0], "1983313086": [0], "1983450664": [0], "1983450722": [0], "1983548392": [0, 1], "1983599829": [0], "1983624170": [0], "1983663765": [0, 1], "1983957320": [0], "1984192526": [0], "1984333139": [0], "1984595736": [0], "1984665109": [0], "1984814373": [0], "1984981103": [0], "1985189834": [0], "1985226118": [0], "1985361181": [0], "1985541610": [0, 1], "1985571268": [0], "1985660064": [0], "1986261938": [0], "1986747452": [0], "1987893247": [0], "1988450755": [0], "1988474563": [0], "1988547292": [0], "1988681178": [0], "1988858604": [0], "1988884670": [0], "1989523366": [0], "1989584217": [0], "1989776207": [0], "1989934258": [0], "1989961124": [0], "1989964245": [0], "1990080130": [0], "1990504505": [0, 1], "1990747346": [0], "1990816950": [0, 1], "1990817656": [0], "1990886590": [0], "1991545231": [0], "1991658284": [0], "1992349074": [0], "1992393091": [0], "1992441153": [0], "1992875926": [0], "1992922276": [0], "1992949780": [0], "1992988993": [0], "1993144375": [0], "1993160581": [0], "1993179009": [0], "1993227214": [0], "1993325089": [0, 1], "1993375916": [0], "1993940032": [0, 1], "1994404926": [0], "1994410714": [0], "1994502388": [0], "1994599955": [0], "1995251538": [0], "1995461061": [0], "1995667619": [0], "1995821613": [0], "1995904304": [0], "1996213354": [0], "1996237236": [0], "1996729484": [0], "1996751117": [0], "1997224880": [0], "1997235258": [0], "1997259769": [0], "1998141596": [0], "1998240664": [0], "2000576311": [0], "2000640580": [0], "2000652457": [0], "2000988163": [0], "2001579561": [0], "2002210197": [0], "2002360025": [0], "2002418308": [0], "2002449571": [0], "2002908389": [0], "2003148519": [0], "2003153009": [0], "2003311601": [0, 1], "2003810889": [0], "2004183165": [0], "2004205759": [0], "2004374726": [0], "2004425345": [0], "2004433749": [0], "2004538240": [0], "2005567515": [0], "2005615309": [0], "2005951057": [0], "2005953552": [0], "2006404240": [0], "2006503656": [0], "2006782764": [0], "2006904322": [0], "2007335111": [0], "2007573143": [0], "2007852226": [0], "2008225828": [0], "2008341690": [0], "2008416631": [0], "2008543548": [0], "2008588633": [0], "2008811486": [0], "2009077907": [0, 1], "2009223686": [0], "2009385770": [0], "2009412606": [0], "2009494285": [0], "2009872684": [0], "2010181875": [0], "2010341827": [0], "2010342892": [0], "2010441741": [0], "2011098234": [0], "2011120006": [0], "2011133604": [0], "2011167724": [0], "2011491125": [0], "2012272721": [0, 1], "2012392467": [0], "2012412128": [0, 1], "2012509857": [0], "2012966526": [0, 1], "2013164502": [0], "2013678152": [0], "2013748354": [0], "2013760456": [0], "2014135111": [0], "2014455371": [0], "2014529003": [0], "2015153169": [0], "2015322067": [0], "2015591304": [0], "2015701126": [0], "2015738422": [0], "2015863800": [0], "2016141421": [0], "2016169118": [0], "2016894532": [0], "2016904661": [0], "2017018294": [0, 1], "2017059839": [0], "2017364102": [0], "2017591414": [0], "2017611169": [0], "2017688721": [0], "2018170871": [0], "2018353722": [0], "2018481965": [0], "2018807986": [0], "2019330954": [0], "2019621292": [0], "2020449260": [0], "2020489512": [0], "2020659448": [0], "2020811612": [0], "2020882106": [0], "2021434882": [0], "2021469891": [0], "2021674170": [0], "2021716496": [0], "2021762279": [0], "2022023559": [0], "2022170127": [0], "2022334829": [0], "2022342141": [0], "2022346401": [0], "2022366468": [0], "2022462997": [0], "2022624225": [0], "2022653887": [0, 1], "2023094867": [0], "2023463802": [0], "2023498630": [0], "2023735770": [0], "2024118348": [0], "2024710752": [0], "2025050667": [0], "2025489818": [0], "2025542794": [0], "2026263838": [0], "2026555237": [0], "2027155651": [0], "2027255768": [0], "2027612802": [0], "2028209854": [0], "2028220218": [0, 1], "2028465934": [0], "2028555449": [0], "2028794217": [0], "2028853686": [0], "2028884263": [0], "2029164013": [0], "2029198776": [0], "2030104832": [0], "2030109735": [0], "2030367676": [0], "2030605424": [0], "2030673708": [0], "2030893891": [0], "2030935105": [0], "2030975326": [0], "2031014845": [0], "2031159827": [0], "2031197764": [0], "2031732058": [0], "2031846085": [0], "2031884889": [0], "2032033636": [0], "2032115185": [0], "2032161661": [0], "2032597674": [0], "2032945709": [0], "2033279647": [0], "2033339834": [0], "2033879503": [0, 1], "2034022808": [0], "2034187078": [0], "2034188062": [0], "2034202412": [0], "2034203709": [0], "2034282053": [0], "2034288231": [0], "2034420916": [0], "2034899507": [0], "2035398439": [0], "2035546101": [0], "2035701971": [0], "2035731851": [0], "2036099989": [0], "2036243799": [0], "2036957242": [0], "2037202633": [0, 1], "2037747666": [0], "2038230627": [0], "2038441823": [0], "2038461568": [0], "2038950002": [0], "2038968275": [0], "2039033861": [0], "2039195676": [0], "2039292003": [0], "2039412625": [0], "2039547685": [0], "2039713262": [0], "2039835282": [0], "2040267390": [0], "2040282775": [0], "2040383713": [0], "2040583491": [0], "2040903938": [0], "2040951645": [0], "2041155066": [0], "2041495932": [0], "2041529046": [0], "2041586300": [0], "2041751399": [0], "2042513143": [0], "2042565820": [0], "2042621845": [0], "2042962050": [0], "2043137160": [0], "2043472906": [0], "2043658528": [0], "2043775369": [0], "2043785761": [0], "2044109311": [0], "2044227628": [0], "2044390270": [0], "2044827486": [0], "2045226373": [0], "2045273640": [0], "2045511235": [0], "2045764556": [0], "2046004150": [0, 1], "2046212554": [0], "2046477182": [0], "2046513282": [0], "2046802090": [0], "2047502827": [0], "2047862358": [0], "2048256140": [0], "2048671024": [0], "2048695455": [0], "2048742662": [0], "2049178586": [0], "2049554619": [0], "2049599882": [0], "2050017307": [0], "2050024633": [0], "2050162535": [0], "2050203756": [0], "2050956437": [0], "2051290887": [0], "2051633994": [0], "2052020264": [0], "2052031772": [0], "2052184537": [0], "2052430011": [0], "2052438990": [0], "2052561765": [0], "2052807698": [0], "2053458268": [0], "2053777987": [0], "2054066272": [0], "2054070983": [0], "2054071579": [0], "2054163756": [0], "2054253766": [0], "2054734903": [0], "2054986850": [0], "2055094741": [0], "2055219898": [0], "2055376323": [0], "2055490850": [0], "2055656305": [0, 1], "2055662659": [0], "2055809946": [0], "2056197587": [0], "2056565578": [0], "2056695963": [0], "2056810648": [0], "2056824218": [0], "2057804457": [0], "2057936724": [0], "2058075225": [0], "2058292154": [0], "2058893857": [0], "2059319442": [0], "2059621621": [0], "2059792873": [0], "2060245937": [0], "2060594747": [0], "2060878905": [0], "2061176946": [0], "2061692068": [0], "2061969639": [0], "2062050568": [0], "2062090335": [0], "2062280024": [0], "2062289397": [0], "2062375736": [0], "2062415334": [0, 1], "2062659708": [0], "2062739107": [0], "2063021390": [0], "2063040889": [0], "2063443904": [0], "2063565527": [0], "2063600825": [0], "2064143499": [0, 1], "2064285316": [0], "2064450173": [0], "2064899230": [0], "2065302654": [0], "2065391218": [0], "2065842117": [0], "2065862272": [0], "2065986467": [0], "2066122166": [0], "2066169643": [0], "2066208355": [0], "2066456374": [0], "2066639392": [0], "2066978239": [0], "2067734010": [0], "2067955613": [0], "2068070850": [0], "2068139291": [0], "2068251365": [0], "2068526934": [0], "2069047798": [0], "2069432180": [0], "2069684356": [0], "2069774092": [0], "2069899728": [0], "2070035738": [0], "2070307717": [0], "2070862432": [0], "2071005950": [0], "2071872677": [0], "2071906770": [0], "2071929350": [0], "2071979040": [0], "2072086947": [0], "2072116577": [0], "2072152475": [0], "2072344611": [0], "2072545487": [0], "2072612912": [0], "2072621666": [0], "2072872778": [0], "2073021279": [0], "2073044895": [0], "2073136744": [0], "2073416593": [0], "2073497367": [0], "2073695864": [0], "2073725809": [0], "2073934018": [0], "2074080576": [0], "2074163552": [0], "2074239336": [0], "2074275655": [0], "2074356459": [0], "2074578977": [0], "2074697128": [0], "2074864992": [0], "2074882092": [0], "2075125272": [0], "2075532051": [0], "2075657247": [0], "2075861484": [0], "2076583843": [0], "2076647486": [0], "2076672198": [0], "2076760966": [0], "2077182517": [0], "2077858333": [0], "2078358672": [0], "2078530927": [0], "2078899207": [0], "2079196604": [0], "2079221146": [0], "2079415511": [0], "2079437377": [0], "2079577635": [0], "2079796021": [0, 1], "2079910665": [0], "2080822238": [0], "2080827119": [0], "2080942386": [0], "2081314051": [0], "2081571294": [0], "2081719610": [0], "2081763420": [0], "2081933235": [0], "2082466116": [0], "2082525092": [0], "2082725360": [0], "2083302132": [0], "2083326496": [0], "2083373059": [0], "2083473209": [0], "2083626551": [0], "2083632734": [0], "2083752202": [0], "2083811866": [0], "2083917474": [0], "2084629699": [0], "2084670092": [0], "2084749768": [0, 1], "2084999229": [0], "2085083966": [0], "2085097274": [0], "2085165014": [0], "2085215024": [0], "2085641399": [0], "2085928209": [0], "2086291519": [0], "2086368224": [0, 1], "2086591542": [0], "2086706112": [0, 1], "2086734052": [0], "2087106688": [0], "2088300578": [0], "2088384498": [0], "2088658236": [0], "2088818189": [0], "2088949640": [0], "2089161021": [0], "2089303283": [0], "2089326714": [0], "2089376020": [0], "2089414517": [0], "2089444009": [0], "2089597487": [0, 1], "2089753463": [0], "2089855986": [0], "2089964008": [0], "2090089020": [0], "2090210030": [0], "2090317611": [0], "2090549934": [0, 1], "2090654508": [0], "2090978819": [0], "2091018987": [0], "2091711980": [0], "2092086939": [0], "2092206474": [0], "2092326719": [0], "2093313706": [0], "2093421316": [0], "2094049794": [0], "2094245165": [0], "2094332170": [0], "2094848516": [0], "2094969066": [0], "2095106825": [0], "2095139478": [0], "2095141672": [0], "2095862940": [0], "2095938871": [0], "2096109787": [0], "2096241408": [0], "2096359021": [0], "2096378837": [0], "2096404641": [0], "2096491061": [0], "2096505967": [0], "2096543098": [0], "2096941444": [0], "2097343707": [0], "2097798760": [0], "2097907328": [0], "2097918448": [0], "2098024905": [0], "2098979881": [0], "2099595443": [0], "2099784578": [0, 1], "2100041804": [0], "2100231020": [0], "2100512369": [0], "2100532600": [0], "2100646040": [0, 1], "2100910742": [0], "2100990133": [0], "2102117544": [0], "2102433870": [0], "2102552588": [0], "2102814959": [0], "2103004617": [0], "2103345539": [0], "2103434945": [0], "2103745298": [0], "2103973306": [0], "2104008474": [0], "2104095032": [0], "2104383983": [0], "2105065272": [0], "2105290129": [0], "2105363496": [0], "2106160078": [0], "2106656433": [0], "2106788989": [0], "2106810382": [0], "2106856803": [0], "2107013891": [0], "2107109471": [0], "2107416910": [0], "2107472364": [0], "2107517042": [0], "2107522414": [0], "2107564948": [0], "2107621900": [0], "2107841760": [0], "2108315102": [0], "2108456400": [0], "2108829040": [0], "2108830769": [0], "2109388045": [0], "2109402179": [0], "2109547045": [0], "2109747393": [0], "2110163135": [0], "2110190317": [0], "2110286199": [0], "2110411883": [0, 1], "2110694573": [0], "2110706753": [0], "2110825687": [0], "2111457038": [0], "2111816391": [0, 1], "2112157052": [0], "2112265233": [0], "2112519680": [0], "2112557677": [0, 1], "2112843985": [0], "2113049936": [0], "2113267667": [0], "2113276908": [0], "2113599514": [0], "2113848229": [0], "2114030427": [0], "2114238829": [0], "2114312485": [0], "2114334002": [0], "2115028399": [0], "2115107662": [0], "2115531380": [0], "2115900840": [0], "2115968501": [0], "2116172231": [0], "2116272060": [0], "2116748855": [0], "2116808803": [0], "2116901537": [0], "2117124371": [0], "2117565791": [0, 1], "2117715258": [0], "2117785843": [0], "2118235503": [0, 1], "2118291107": [0], "2118320014": [0], "2118579495": [0], "2118649620": [0], "2118734099": [0], "2118774081": [0, 1], "2119628576": [0], "2120354059": [0], "2121009473": [0], "2121227868": [0], "2121277037": [0], "2121356320": [0], "2121551751": [0], "2121683906": [0], "2122361229": [0], "2122841775": [0], "2122944552": [0], "2122991223": [0], "2123271985": [0], "2123325808": [0], "2123375083": [0], "2123511810": [0], "2123523754": [0], "2123562770": [0, 1], "2124174879": [0], "2124277427": [0], "2124355491": [0], "2124687739": [0], "2124709362": [0], "2124995711": [0], "2125103016": [0], "2125639138": [0, 1], "2125640335": [0], "2125745239": [0], "2125808557": [0], "2125836107": [0], "2126324214": [0], "2126540602": [0], "2126601173": [0], "2127271667": [0], "2127458170": [0], "2127537146": [0], "2127554534": [0], "2127682859": [0], "2127871525": [0, 1], "2127942910": [0], "2127960253": [0], "2128192928": [0], "2128490534": [0], "2128501706": [0], "2128560661": [0], "2128694425": [0], "2129182600": [0], "2129787619": [0], "2130448955": [0], "2130494392": [0], "2130742419": [0], "2130798060": [0], "2130868226": [0], "2130892706": [0], "2131080780": [0], "2131239827": [0], "2131567732": [0, 1], "2131917169": [0], "2132540681": [0], "2132599753": [0], "2132876998": [0], "2132894179": [0], "2133060353": [0], "2133343566": [0], "2133404423": [0], "2133781199": [0, 1], "2134057576": [0], "2136328094": [0], "2136387096": [0], "2136684259": [0], "2137601794": [0], "2137640377": [0], "2137803641": [0], "2138154394": [0], "2138165549": [0], "2138448229": [0], "2138674848": [0], "2138962718": [0], "2139228842": [0], "2139618164": [0], "2139719691": [0], "2139734849": [0], "2140044661": [0], "2140122631": [0], "2140127395": [0, 1], "2140195581": [0], "2140691244": [0], "2140945810": [0], "2141214889": [0], "2141402934": [0], "2142176214": [0], "2142246180": [0], "2143037834": [0], "2143350021": [0], "2144019816": [0], "2144130013": [0], "2144247689": [0], "2144303596": [0], "2144537917": [0], "2144827463": [0, 1], "2145092867": [0, 1], "2145483743": [0], "2145533679": [0], "2145565430": [0], "2146222159": [0], "2146309399": [0], "2146380130": [0], "2146427829": [0], "2146550030": [0], "524640": [1], "996218": [1], "1677842": [1], "1943998": [1], "3027412": [1], "3565461": [1], "3850568": [1], "4033372": [1], "4128345": [1], "4481770": [1], "4805405": [1], "4991922": [1], "6301343": [1], "6339869": [1], "6478552": [1], "6902783": [1], "7350539": [1], "7403604": [1], "7728335": [1], "8402924": [1], "8860051": [1], "9101513": [1], "9217586": [1], "10948809": [1], "11017359": [1], "11058696": [1], "11464875": [1], "11500707": [1], "11755451": [1], "11765822": [1], "12075305": [1], "12103558": [1], "12122884": [1], "12123911": [1], "12313842": [1], "12432614": [1], "12931139": [1], "13441988": [1], "13626808": [1], "13926380": [1], "14055738": [1], "14084151": [1], "14233234": [1], "14592715": [1], "14649085": [1], "14742061": [1], "15218296": [1], "15400279": [1], "15715082": [1], "15789754": [1], "16047683": [1], "16421779": [1], "16673083": [1], "16711337": [1], "16751005": [1], "16845050": [1], "17081455": [1], "17300968": [1], "17339520": [1], "18209280": [1], "19134435": [1], "19297214": [1], "19494947": [1], "19641766": [1], "19756685": [1], "19867382": [1], "20894079": [1], "21107855": [1], "21177166": [1], "21381403": [1], "21651971": [1], "22046674": [1], "22350713": [1], "22549848": [1], "22593972": [1], "22747702": [1], "22964921": [1], "23269854": [1], "23762388": [1], "23853536": [1], "23882570": [1], "23990657": [1], "24169867": [1], "24285356": [1], "24303168": [1], "24321305": [1], "24400603": [1], "24636535": [1], "24818259": [1], "25005444": [1], "25226667": [1], "25638559": [1], "25643018": [1], "25858165": [1], "25932677": [1], "26127384": [1], "26250253": [1], "26364449": [1], "26499458": [1], "26735315": [1], "26974569": [1], "27091987": [1], "27429828": [1], "27566876": [1], "27807937": [1], "27974541": [1], "28281612": [1], "28319516": [1], "28630550": [1], "28650193": [1], "29394916": [1], "29509629": [1], "29774794": [1], "30157518": [1], "30211074": [1], "30638615": [1], "31077595": [1], "31391351": [1], "31572034": [1], "31918606": [1], "32032417": [1], "32248197": [1], "32523852": [1], "32980767": [1], "33232182": [1], "33428624": [1], "33541813": [1], "33583915": [1], "33755852": [1], "33814442": [1], "33992259": [1], "34298949": [1], "34459400": [1], "34909801": [1], "35125618": [1], "35204842": [1], "35823695": [1], "36768877": [1], "36998508": [1], "37037013": [1], "37304271": [1], "37306378": [1], "37422884": [1], "38432995": [1], "39296924": [1], "39633027": [1], "39719173": [1], "39844544": [1], "39953751": [1], "40206210": [1], "40565199": [1], "40740207": [1], "40750498": [1], "41083317": [1], "41088996": [1], "41587768": [1], "41902424": [1], "42150835": [1], "42236383": [1], "42484406": [1], "43099731": [1], "43269188": [1], "43378637": [1], "43985559": [1], "44047580": [1], "44284800": [1], "44316282": [1], "44499252": [1], "44850406": [1], "44874045": [1], "44876824": [1], "44986468": [1], "45075628": [1], "45130444": [1], "45131062": [1], "45307429": [1], "45353726": [1], "46075997": [1], "46328102": [1], "46350985": [1], "46479223": [1], "46503958": [1], "46880778": [1], "46908272": [1], "46932683": [1], "47284164": [1], "47502801": [1], "47535268": [1], "47835476": [1], "47927801": [1], "48393968": [1], "48547630": [1], "48640973": [1], "48711734": [1], "48774667": [1], "49187145": [1], "49862485": [1], "49916719": [1], "49990733": [1], "50507897": [1], "50813047": [1], "50989667": [1], "51245217": [1], "51554860": [1], "51629074": [1], "51744787": [1], "51870507": [1], "51965288": [1], "52575667": [1], "52659948": [1], "53204357": [1], "53487930": [1], "53741859": [1], "53802920": [1], "53860101": [1], "53895004": [1], "53928001": [1], "54569338": [1], "54665705": [1], "56357561": [1], "56622931": [1], "56659614": [1], "56816463": [1], "56910563": [1], "57258375": [1], "57564955": [1], "57753774": [1], "57921730": [1], "58156994": [1], "58220810": [1], "58534111": [1], "59387405": [1], "59585087": [1], "60128224": [1], "60654481": [1], "60945385": [1], "61015924": [1], "61361389": [1], "61481901": [1], "61796956": [1], "62023795": [1], "62108788": [1], "62174914": [1], "62638875": [1], "62730156": [1], "62733350": [1], "62791255": [1], "63047090": [1], "63195455": [1], "63235848": [1], "63269527": [1], "63357552": [1], "63671393": [1], "63706807": [1], "63926445": [1], "64618870": [1], "64863971": [1], "64952357": [1], "65048151": [1], "65154680": [1], "65193237": [1], "65349449": [1], "65370257": [1], "65488984": [1], "65612297": [1], "65646554": [1], "65669782": [1], "66158015": [1], "66713330": [1], "66936833": [1], "66966055": [1], "67020999": [1], "67152431": [1], "67370260": [1], "67594153": [1], "67718709": [1], "67962498": [1], "68254181": [1], "68527850": [1], "68953293": [1], "69027804": [1], "69238325": [1], "69930831": [1], "70142818": [1], "70266607": [1], "70298545": [1], "70519418": [1], "71018599": [1], "71060708": [1], "71065366": [1], "71111670": [1], "71465982": [1], "71608945": [1], "71802616": [1], "71931409": [1], "72171849": [1], "72200206": [1], "72311677": [1], "72326671": [1], "72331498": [1], "72416194": [1], "72426010": [1], "72519902": [1], "72793088": [1], "72860527": [1], "73548031": [1], "73751261": [1], "73881513": [1], "74060279": [1], "74367788": [1], "74372876": [1], "75387065": [1], "75491488": [1], "75546677": [1], "76085861": [1], "76188436": [1], "76211682": [1], "76452092": [1], "76612607": [1], "76740599": [1], "76780720": [1], "76822113": [1], "77012621": [1], "77403704": [1], "77404126": [1], "77534269": [1], "77622805": [1], "77822563": [1], "78589458": [1], "78633528": [1], "78910462": [1], "79371609": [1], "79396058": [1], "79449452": [1], "80183091": [1], "80321010": [1], "80373955": [1], "80539542": [1], "80750560": [1], "80785591": [1], "80857151": [1], "80878357": [1], "80947196": [1], "81293778": [1], "81463166": [1], "81714734": [1], "81730850": [1], "81894297": [1], "82174310": [1], "82680491": [1], "82797516": [1], "82881281": [1], "83189522": [1], "83456424": [1], "83849169": [1], "84070997": [1], "84229878": [1], "84295082": [1], "84336424": [1], "84694858": [1], "85578678": [1], "85734091": [1], "85893151": [1], "85975241": [1], "86450291": [1], "86475872": [1], "86575796": [1], "86681859": [1], "86818654": [1], "86865849": [1], "87163429": [1], "87175780": [1], "87260714": [1], "87550624": [1], "87737476": [1], "88374743": [1], "88558941": [1], "88727996": [1], "88838910": [1], "88869138": [1], "88958218": [1], "88958694": [1], "89023214": [1], "89055546": [1], "89221330": [1], "89422659": [1], "89769367": [1], "90038731": [1], "90103076": [1], "90242882": [1], "90376339": [1], "90505787": [1], "90554534": [1], "90724786": [1], "90913394": [1], "91146651": [1], "91231940": [1], "91350859": [1], "91467872": [1], "92020432": [1], "92087290": [1], "92279786": [1], "92701107": [1], "92826610": [1], "92867565": [1], "93169210": [1], "93202968": [1], "93568565": [1], "93620330": [1], "93697297": [1], "93870926": [1], "94083287": [1], "94165790": [1], "94341281": [1], "94516502": [1], "95090057": [1], "95126155": [1], "95230715": [1], "95284255": [1], "95940420": [1], "96491841": [1], "96614464": [1], "96627662": [1], "96701304": [1], "96749938": [1], "96870691": [1], "97697378": [1], "97859212": [1], "98256212": [1], "98455369": [1], "98484849": [1], "98765157": [1], "98795473": [1], "99087678": [1], "99091654": [1], "99365599": [1], "99494025": [1], "99629560": [1], "99808022": [1], "99934113": [1], "100245579": [1], "100546345": [1], "100703648": [1], "101106505": [1], "101142706": [1], "101674354": [1], "101725964": [1], "101944885": [1], "102246924": [1], "102457140": [1], "102541202": [1], "102740422": [1], "102875472": [1], "103109208": [1], "103367736": [1], "103467554": [1], "103679803": [1], "104066784": [1], "104377532": [1], "104787081": [1], "105098594": [1], "105105922": [1], "105397485": [1], "105934112": [1], "106138203": [1], "106787451": [1], "107493831": [1], "107501253": [1], "107886820": [1], "108030953": [1], "108071545": [1], "108163114": [1], "108499191": [1], "108542076": [1], "108645436": [1], "108807650": [1], "108984881": [1], "109153681": [1], "109163072": [1], "109442237": [1], "109692074": [1], "109747766": [1], "110650055": [1], "110713510": [1], "111066331": [1], "111168373": [1], "111735202": [1], "112127025": [1], "112423866": [1], "112454056": [1], "112662903": [1], "112718965": [1], "112740397": [1], "113572121": [1], "114155027": [1], "114197737": [1], "114238650": [1], "114352463": [1], "114511490": [1], "115149116": [1], "115406239": [1], "115933236": [1], "116066063": [1], "116293291": [1], "116325982": [1], "116460548": [1], "116778298": [1], "116798167": [1], "116872125": [1], "117045881": [1], "117593331": [1], "117728873": [1], "117832806": [1], "117993649": [1], "118267196": [1], "118356909": [1], "118517868": [1], "118569202": [1], "118589372": [1], "118589900": [1], "119499722": [1], "119505512": [1], "119760097": [1], "119987898": [1], "120098502": [1], "120105998": [1], "121066584": [1], "121422284": [1], "121700965": [1], "121776807": [1], "122395496": [1], "122562763": [1], "122790275": [1], "123346968": [1], "123441439": [1], "123551498": [1], "123691501": [1], "124137375": [1], "124469137": [1], "124625050": [1], "124768472": [1], "124920966": [1], "125145381": [1], "126130167": [1], "126667378": [1], "126733749": [1], "126741981": [1], "127107432": [1], "127303182": [1], "127329375": [1], "127790355": [1], "127980392": [1], "128213661": [1], "128245773": [1], "128370568": [1], "128989572": [1], "129003149": [1], "129051083": [1], "129113311": [1], "129299370": [1], "129337965": [1], "129378946": [1], "129954524": [1], "130644999": [1], "131073177": [1], "131173654": [1], "131199624": [1], "131290494": [1], "131362655": [1], "131570039": [1], "131874120": [1], "131951616": [1], "132062509": [1], "132864295": [1], "132992451": [1], "134405093": [1], "134443241": [1], "134577459": [1], "135017050": [1], "135599690": [1], "135738009": [1], "135847168": [1], "136483247": [1], "136717915": [1], "137343260": [1], "137353924": [1], "137399361": [1], "137506903": [1], "137711637": [1], "137745268": [1], "137816615": [1], "138021123": [1], "138373891": [1], "138426872": [1], "138909193": [1], "138954967": [1], "138976178": [1], "139167924": [1], "139462593": [1], "140500710": [1], "140632612": [1], "141041347": [1], "141049217": [1], "141063129": [1], "141148933": [1], "141667057": [1], "142148975": [1], "142158561": [1], "142192246": [1], "142231013": [1], "142807393": [1], "142813305": [1], "142878623": [1], "142957187": [1], "143162809": [1], "143569300": [1], "143693936": [1], "143790108": [1], "143945412": [1], "144088044": [1], "145373434": [1], "145418621": [1], "145483061": [1], "145972293": [1], "146107443": [1], "146274757": [1], "146828142": [1], "147233913": [1], "147296594": [1], "147605926": [1], "148062832": [1], "148388485": [1], "148568057": [1], "149309726": [1], "149850992": [1], "149854242": [1], "149994677": [1], "150322630": [1], "150715990": [1], "150781196": [1], "151202457": [1], "151420425": [1], "151705184": [1], "151807224": [1], "152563315": [1], "153064360": [1], "153405644": [1], "153451905": [1], "153458537": [1], "153639800": [1], "153754110": [1], "153963636": [1], "154050579": [1], "154366668": [1], "154526417": [1], "154872477": [1], "155283250": [1], "155888235": [1], "155899106": [1], "156011863": [1], "156240646": [1], "156279037": [1], "156310892": [1], "156322606": [1], "156355508": [1], "156595286": [1], "156603644": [1], "156851979": [1], "156864675": [1], "156867741": [1], "157064131": [1], "157070477": [1], "157408022": [1], "157588143": [1], "157696876": [1], "157779076": [1], "158087681": [1], "158131787": [1], "158306485": [1], "158452351": [1], "158629484": [1], "158654977": [1], "159866857": [1], "160376291": [1], "160619034": [1], "161074675": [1], "161945270": [1], "162495264": [1], "162519527": [1], "162536002": [1], "162605310": [1], "162839209": [1], "163249116": [1], "163664941": [1], "163940407": [1], "164112589": [1], "164552760": [1], "164576916": [1], "164974914": [1], "165115272": [1], "165577179": [1], "165733290": [1], "166203327": [1], "166216113": [1], "167610738": [1], "167924766": [1], "168063177": [1], "168158314": [1], "168436800": [1], "168524706": [1], "168910774": [1], "169038311": [1], "169113175": [1], "169494919": [1], "169694195": [1], "169813092": [1], "170003054": [1], "170064470": [1], "170227426": [1], "170335025": [1], "170406610": [1], "170625858": [1], "170825085": [1], "170927372": [1], "170988222": [1], "171127939": [1], "171279228": [1], "171498761": [1], "171517933": [1], "171544720": [1], "172086709": [1], "172424983": [1], "172508564": [1], "172788176": [1], "172816795": [1], "172847787": [1], "172868777": [1], "172975274": [1], "173118023": [1], "173125661": [1], "173229865": [1], "173295065": [1], "173401168": [1], "173450953": [1], "173831123": [1], "174278026": [1], "174366691": [1], "174731139": [1], "175044127": [1], "175340824": [1], "175938616": [1], "176192581": [1], "176269879": [1], "176782082": [1], "177003269": [1], "177092215": [1], "177150857": [1], "177287209": [1], "177446431": [1], "177628029": [1], "177929419": [1], "178348087": [1], "178469796": [1], "178579397": [1], "178789173": [1], "179257502": [1], "179414239": [1], "179861593": [1], "179894617": [1], "180138875": [1], "180314875": [1], "180454448": [1], "180500228": [1], "180545118": [1], "181027065": [1], "181250916": [1], "181660213": [1], "182363839": [1], "182414149": [1], "182529801": [1], "182836301": [1], "182944548": [1], "183012645": [1], "183349942": [1], "183451271": [1], "183768416": [1], "184008283": [1], "184339009": [1], "184416016": [1], "184656023": [1], "185317318": [1], "185342234": [1], "186046473": [1], "186218965": [1], "186547249": [1], "186666164": [1], "186898508": [1], "187065357": [1], "187071369": [1], "187292415": [1], "187439470": [1], "187552167": [1], "188484257": [1], "188494757": [1], "188854632": [1], "189330893": [1], "189861577": [1], "190266381": [1], "190669326": [1], "190870471": [1], "190961512": [1], "191205792": [1], "191767079": [1], "191888495": [1], "192395955": [1], "192777855": [1], "193071273": [1], "193329936": [1], "193507726": [1], "193551880": [1], "194208489": [1], "194682787": [1], "194686018": [1], "194790008": [1], "195310772": [1], "195718288": [1], "195735370": [1], "195793886": [1], "196607055": [1], "196665288": [1], "196728641": [1], "196790001": [1], "197680476": [1], "197951573": [1], "198015628": [1], "198276844": [1], "198806776": [1], "198921126": [1], "199175501": [1], "199393641": [1], "199526099": [1], "199601550": [1], "200420070": [1], "200637050": [1], "200855279": [1], "201435363": [1], "201733398": [1], "201796692": [1], "202016422": [1], "202230198": [1], "202391916": [1], "202826727": [1], "202931228": [1], "203002884": [1], "203072301": [1], "203222109": [1], "203508252": [1], "203508660": [1], "203545068": [1], "204244482": [1], "204405432": [1], "204472383": [1], "204829731": [1], "204860667": [1], "204902746": [1], "205440407": [1], "205606700": [1], "205629065": [1], "206081214": [1], "206119706": [1], "206301005": [1], "206501268": [1], "206608539": [1], "206754366": [1], "206922674": [1], "207131757": [1], "207801164": [1], "207860724": [1], "207888531": [1], "207900843": [1], "207972857": [1], "208368715": [1], "208480180": [1], "209657949": [1], "209691003": [1], "209784326": [1], "209801537": [1], "209914928": [1], "210071384": [1], "210216799": [1], "210245489": [1], "210639112": [1], "211017938": [1], "211078060": [1], "211315599": [1], "211448493": [1], "211663498": [1], "212272667": [1], "212487244": [1], "212500773": [1], "212631640": [1], "212746152": [1], "212922412": [1], "212944563": [1], "212968122": [1], "213062940": [1], "213414587": [1], "213604050": [1], "213904040": [1], "213917665": [1], "214039445": [1], "214310325": [1], "215628053": [1], "215989271": [1], "216296956": [1], "216590684": [1], "216663399": [1], "216917664": [1], "216949710": [1], "217187377": [1], "217381054": [1], "217660264": [1], "217899215": [1], "217974417": [1], "218117234": [1], "218213936": [1], "218265119": [1], "218417171": [1], "218481049": [1], "218574236": [1], "218665123": [1], "218967735": [1], "219100641": [1], "219140170": [1], "219514256": [1], "220101705": [1], "220245542": [1], "220412978": [1], "220496396": [1], "220517525": [1], "221096628": [1], "221542603": [1], "222304177": [1], "222620414": [1], "222635336": [1], "222665051": [1], "222805140": [1], "223058451": [1], "223110147": [1], "223350029": [1], "223484965": [1], "223538461": [1], "223676230": [1], "223785891": [1], "225436838": [1], "225458077": [1], "225521161": [1], "225688608": [1], "227103977": [1], "227621779": [1], "227902694": [1], "227935106": [1], "228284706": [1], "228297419": [1], "228514483": [1], "229156795": [1], "229345787": [1], "229471465": [1], "229594240": [1], "230000596": [1], "230134754": [1], "230188601": [1], "230233532": [1], "230496069": [1], "231143353": [1], "231933203": [1], "231986748": [1], "232234020": [1], "232548901": [1], "232695746": [1], "232700774": [1], "232904692": [1], "233896966": [1], "233977754": [1], "234039939": [1], "234632811": [1], "234747452": [1], "234882194": [1], "234937584": [1], "235652718": [1], "235906065": [1], "236015399": [1], "236049114": [1], "236104433": [1], "236279529": [1], "236753035": [1], "236931591": [1], "237089065": [1], "238045179": [1], "238118575": [1], "238354651": [1], "239090623": [1], "239165540": [1], "239895579": [1], "240013985": [1], "240228220": [1], "240601318": [1], "241070279": [1], "241305359": [1], "241721744": [1], "241728634": [1], "241928146": [1], "241953889": [1], "241972238": [1], "242118293": [1], "242182856": [1], "242333716": [1], "242527850": [1], "242911814": [1], "243081327": [1], "243118619": [1], "243120535": [1], "243289061": [1], "243669151": [1], "243680800": [1], "243988407": [1], "244120449": [1], "244233992": [1], "244729829": [1], "244817948": [1], "245039345": [1], "245228168": [1], "245547199": [1], "245861874": [1], "245878006": [1], "246314826": [1], "246335247": [1], "246616359": [1], "246807357": [1], "247172574": [1], "247546861": [1], "247552017": [1], "247618421": [1], "247916145": [1], "248301216": [1], "248370659": [1], "248985874": [1], "249013642": [1], "249063634": [1], "249808977": [1], "249842327": [1], "249997038": [1], "250286606": [1], "250434860": [1], "250490373": [1], "250989225": [1], "250999007": [1], "251139615": [1], "251286569": [1], "251449786": [1], "251555924": [1], "251592639": [1], "251923461": [1], "251944711": [1], "252234245": [1], "252806115": [1], "253298755": [1], "253401378": [1], "253603080": [1], "254011692": [1], "254263251": [1], "254553139": [1], "254972660": [1], "255224880": [1], "255368848": [1], "255645084": [1], "255672865": [1], "255713620": [1], "255738875": [1], "255989006": [1], "256010385": [1], "256348989": [1], "256529878": [1], "256554642": [1], "256816497": [1], "257183843": [1], "257881871": [1], "257994202": [1], "258065389": [1], "258807551": [1], "258843104": [1], "259018494": [1], "259533525": [1], "260013147": [1], "260249068": [1], "260360974": [1], "261738064": [1], "262115378": [1], "262161622": [1], "262625684": [1], "262929617": [1], "263546989": [1], "263800848": [1], "263884416": [1], "264014158": [1], "264207465": [1], "264313940": [1], "264577230": [1], "264601988": [1], "264783471": [1], "265122574": [1], "265858552": [1], "266043887": [1], "266174168": [1], "266421410": [1], "266586283": [1], "266957001": [1], "267243658": [1], "267357845": [1], "267882653": [1], "268062283": [1], "268207732": [1], "268545652": [1], "269041266": [1], "269095595": [1], "269155791": [1], "269413869": [1], "269441467": [1], "269585196": [1], "269599321": [1], "269670973": [1], "269759797": [1], "269832659": [1], "270127480": [1], "270487180": [1], "270798084": [1], "270959235": [1], "271142002": [1], "271144637": [1], "271235051": [1], "271301562": [1], "271323161": [1], "271368116": [1], "271706550": [1], "271797441": [1], "271999159": [1], "272081323": [1], "272934779": [1], "273124630": [1], "273585150": [1], "273832793": [1], "273923567": [1], "274129572": [1], "274186104": [1], "274383675": [1], "274419508": [1], "274499571": [1], "274762281": [1], "274907964": [1], "275027590": [1], "275185763": [1], "275478811": [1], "275502433": [1], "275779907": [1], "276029102": [1], "276213260": [1], "276279079": [1], "276746291": [1], "277116486": [1], "277199187": [1], "277311452": [1], "277465086": [1], "278087193": [1], "278121242": [1], "278223510": [1], "278255335": [1], "278413517": [1], "278605586": [1], "278867154": [1], "279501259": [1], "279519867": [1], "280049986": [1], "280272281": [1], "281869850": [1], "281917084": [1], "281963936": [1], "282268705": [1], "282923003": [1], "283505736": [1], "283958365": [1], "284105323": [1], "284398056": [1], "285091997": [1], "285280214": [1], "285360868": [1], "285504777": [1], "286455114": [1], "286636036": [1], "286989457": [1], "287205254": [1], "287480454": [1], "288270795": [1], "288551633": [1], "288674872": [1], "288950335": [1], "289079577": [1], "289194973": [1], "289326707": [1], "289331151": [1], "289446035": [1], "289590411": [1], "289652385": [1], "290018705": [1], "290033579": [1], "290055554": [1], "290118506": [1], "290193250": [1], "290293451": [1], "290548753": [1], "290620641": [1], "290786302": [1], "291251829": [1], "291517340": [1], "291591552": [1], "291654987": [1], "291734677": [1], "291990421": [1], "292222140": [1], "292493553": [1], "292548062": [1], "292768894": [1], "293395082": [1], "293462408": [1], "293606582": [1], "293780428": [1], "293790156": [1], "294261371": [1], "294539017": [1], "294722616": [1], "294738283": [1], "294825865": [1], "294886770": [1], "295083250": [1], "295290793": [1], "295315475": [1], "295448739": [1], "295557860": [1], "295591314": [1], "295685456": [1], "295953074": [1], "296090477": [1], "296149954": [1], "296210657": [1], "296293212": [1], "296709897": [1], "297050766": [1], "297225301": [1], "297232504": [1], "297331406": [1], "297682674": [1], "297704400": [1], "297794314": [1], "297892347": [1], "298537200": [1], "298648862": [1], "298668924": [1], "298940869": [1], "299044210": [1], "299202615": [1], "299226047": [1], "299529033": [1], "299614084": [1], "299617200": [1], "299844130": [1], "299868799": [1], "300313329": [1], "300468922": [1], "301804455": [1], "302045285": [1], "302279500": [1], "302845256": [1], "303242740": [1], "303364712": [1], "303383261": [1], "303450515": [1], "304089621": [1], "304295736": [1], "304767749": [1], "304869107": [1], "304961229": [1], "304969560": [1], "305027341": [1], "305029662": [1], "305075906": [1], "305226207": [1], "305329786": [1], "305557768": [1], "306033625": [1], "306081518": [1], "306103116": [1], "306171097": [1], "306339465": [1], "306358517": [1], "307013948": [1], "307393388": [1], "307421688": [1], "307472558": [1], "307492605": [1], "307568491": [1], "307762602": [1], "308068660": [1], "308299850": [1], "308490957": [1], "308637644": [1], "308877667": [1], "309165834": [1], "309245702": [1], "309931200": [1], "310124877": [1], "310296694": [1], "310647475": [1], "310677849": [1], "310728786": [1], "311047621": [1], "311199952": [1], "311625565": [1], "311975207": [1], "312014195": [1], "312258981": [1], "312340912": [1], "312938210": [1], "312939247": [1], "312981591": [1], "313159114": [1], "313328914": [1], "313659058": [1], "313875732": [1], "313954623": [1], "314194223": [1], "314451462": [1], "314715111": [1], "314786208": [1], "314958704": [1], "315068125": [1], "315226530": [1], "315332038": [1], "315408836": [1], "315513029": [1], "315631486": [1], "315722284": [1], "316320561": [1], "316353644": [1], "316367885": [1], "316479907": [1], "316488296": [1], "316632863": [1], "316750500": [1], "316813236": [1], "317144332": [1], "317177995": [1], "317534334": [1], "317851713": [1], "318028641": [1], "318030721": [1], "318126200": [1], "318712291": [1], "318842228": [1], "319265866": [1], "319606044": [1], "319641270": [1], "319831217": [1], "319913817": [1], "320256724": [1], "320502650": [1], "320729879": [1], "320830387": [1], "320923755": [1], "321462178": [1], "321542761": [1], "321619187": [1], "321728818": [1], "322325692": [1], "322924665": [1], "322990108": [1], "323029286": [1], "323138524": [1], "323335512": [1], "323425743": [1], "323573662": [1], "323709185": [1], "323823939": [1], "323934876": [1], "324454908": [1], "324740867": [1], "324807765": [1], "325058611": [1], "325387698": [1], "325517943": [1], "325579746": [1], "325913391": [1], "325916483": [1], "325983601": [1], "326191017": [1], "326440628": [1], "326790136": [1], "327251688": [1], "327325624": [1], "327380354": [1], "328035450": [1], "328111344": [1], "328134990": [1], "328376968": [1], "328416338": [1], "328505460": [1], "328643142": [1], "328709416": [1], "329077294": [1], "329300643": [1], "329605894": [1], "329625861": [1], "330165185": [1], "330314991": [1], "330386231": [1], "330914814": [1], "331217657": [1], "331265090": [1], "331327654": [1], "331451351": [1], "332565308": [1], "332598559": [1], "333078855": [1], "333819950": [1], "333834410": [1], "333992477": [1], "334038581": [1], "334282115": [1], "334415951": [1], "334429908": [1], "334844297": [1], "335003375": [1], "335058169": [1], "335551679": [1], "335735288": [1], "335751920": [1], "335819812": [1], "336179429": [1], "336519609": [1], "336575618": [1], "336884782": [1], "337267750": [1], "337414414": [1], "337702649": [1], "338123543": [1], "338327300": [1], "338367888": [1], "338610181": [1], "339032283": [1], "339293991": [1], "339305404": [1], "339595166": [1], "339749162": [1], "340273229": [1], "340542617": [1], "340817756": [1], "340893662": [1], "341162351": [1], "341194423": [1], "341203528": [1], "341533260": [1], "341615637": [1], "341617000": [1], "341626922": [1], "341737545": [1], "342125914": [1], "342355187": [1], "343015075": [1], "343233853": [1], "343405636": [1], "343835622": [1], "343843852": [1], "343962700": [1], "344264090": [1], "344858692": [1], "344922198": [1], "345007981": [1], "345479003": [1], "345481252": [1], "345579228": [1], "345803783": [1], "346079979": [1], "346108855": [1], "346465218": [1], "347248450": [1], "347414750": [1], "347593878": [1], "347720409": [1], "348024920": [1], "348392754": [1], "348603813": [1], "348676333": [1], "348699604": [1], "348935189": [1], "348937319": [1], "349069045": [1], "349190028": [1], "349200263": [1], "349432199": [1], "349448434": [1], "349729848": [1], "349744568": [1], "349837271": [1], "350037481": [1], "350145260": [1], "350367714": [1], "350672011": [1], "350778467": [1], "350797896": [1], "351000106": [1], "351016405": [1], "351458635": [1], "351694684": [1], "352011518": [1], "352021927": [1], "352233180": [1], "352327351": [1], "352561632": [1], "353526387": [1], "354176154": [1], "354191439": [1], "354221941": [1], "355311276": [1], "355312001": [1], "355869319": [1], "356130237": [1], "356849331": [1], "357015948": [1], "358017376": [1], "358386881": [1], "358699891": [1], "358839777": [1], "358876141": [1], "359733491": [1], "361077899": [1], "361194043": [1], "361544613": [1], "362007372": [1], "362016204": [1], "362056464": [1], "362825019": [1], "362970994": [1], "362983288": [1], "363147748": [1], "363522843": [1], "363719772": [1], "363942408": [1], "364007086": [1], "365690526": [1], "366067451": [1], "366121202": [1], "366350781": [1], "366591470": [1], "366665298": [1], "366972362": [1], "367080185": [1], "367125865": [1], "367605743": [1], "367665682": [1], "367752952": [1], "367835362": [1], "368023959": [1], "368358026": [1], "368740892": [1], "369010829": [1], "369198732": [1], "369290612": [1], "369310973": [1], "369321331": [1], "369839798": [1], "369984623": [1], "370357635": [1], "370370141": [1], "371358567": [1], "371543873": [1], "371670492": [1], "371915552": [1], "372030241": [1], "372047224": [1], "372144871": [1], "373176560": [1], "373416462": [1], "373735149": [1], "373762609": [1], "373871485": [1], "374047782": [1], "374168918": [1], "374227127": [1], "374364782": [1], "374741638": [1], "374927854": [1], "375103163": [1], "375184017": [1], "375261718": [1], "375267369": [1], "375347411": [1], "376409198": [1], "376461094": [1], "376487570": [1], "377015568": [1], "377280416": [1], "377733796": [1], "378002558": [1], "378762257": [1], "379204211": [1], "379599807": [1], "379633869": [1], "379704700": [1], "379827743": [1], "379981628": [1], "380034843": [1], "380092763": [1], "380111630": [1], "380986778": [1], "381243510": [1], "381378126": [1], "381527700": [1], "381929076": [1], "381934617": [1], "382009532": [1], "382228523": [1], "382315625": [1], "382578152": [1], "383101522": [1], "383425518": [1], "383644867": [1], "383839632": [1], "384023192": [1], "384131985": [1], "384559779": [1], "384582770": [1], "384623281": [1], "384814674": [1], "385171568": [1], "386017061": [1], "386229687": [1], "386258341": [1], "386342866": [1], "386703527": [1], "387013106": [1], "387131840": [1], "387145123": [1], "387177900": [1], "387551093": [1], "387634697": [1], "388569409": [1], "388749139": [1], "388787186": [1], "389068300": [1], "389272367": [1], "389410277": [1], "389626504": [1], "389665213": [1], "390841838": [1], "391586965": [1], "392185650": [1], "392375453": [1], "392515607": [1], "393021402": [1], "393406151": [1], "393451741": [1], "393727042": [1], "394258713": [1], "394562314": [1], "394571818": [1], "394580247": [1], "394620804": [1], "394693320": [1], "395007494": [1], "395105965": [1], "395526267": [1], "396714220": [1], "397208823": [1], "397853888": [1], "398289537": [1], "398423181": [1], "398712729": [1], "398717031": [1], "398859609": [1], "399077395": [1], "399106731": [1], "399599943": [1], "400706092": [1], "401035127": [1], "401258631": [1], "401588507": [1], "401841230": [1], "401850763": [1], "401960567": [1], "402517005": [1], "402665391": [1], "403238585": [1], "403715667": [1], "403826595": [1], "404398864": [1], "404818472": [1], "404832278": [1], "405052754": [1], "405330904": [1], "405374285": [1], "405652714": [1], "406220663": [1], "406672526": [1], "406928019": [1], "407185004": [1], "407286846": [1], "407665004": [1], "407751173": [1], "407756956": [1], "407893777": [1], "408398630": [1], "408411629": [1], "408441403": [1], "409069394": [1], "410067928": [1], "410376013": [1], "410832727": [1], "410930210": [1], "411194372": [1], "411310034": [1], "411368828": [1], "411640388": [1], "411968386": [1], "412243044": [1], "412407391": [1], "413131439": [1], "413200087": [1], "413385579": [1], "413620673": [1], "414600029": [1], "414726717": [1], "414936563": [1], "415135939": [1], "415199720": [1], "415414789": [1], "415505383": [1], "415732893": [1], "415802328": [1], "416156447": [1], "416208093": [1], "416573431": [1], "417040884": [1], "417762465": [1], "418033474": [1], "418130083": [1], "418133241": [1], "418193399": [1], "418422085": [1], "418457286": [1], "418587384": [1], "418625186": [1], "418951298": [1], "419513212": [1], "419717049": [1], "420643991": [1], "420704605": [1], "421374620": [1], "421921955": [1], "422084564": [1], "422103004": [1], "422257993": [1], "422742327": [1], "423188560": [1], "423216571": [1], "423814221": [1], "423877339": [1], "424035705": [1], "424499753": [1], "424542595": [1], "424914331": [1], "424915253": [1], "425186852": [1], "425277728": [1], "425639186": [1], "425724988": [1], "425777993": [1], "426006949": [1], "426475305": [1], "428032668": [1], "428271779": [1], "428288078": [1], "428325507": [1], "428504092": [1], "428520937": [1], "428594435": [1], "429263738": [1], "429341087": [1], "429407070": [1], "429712070": [1], "429755246": [1], "429841530": [1], "430080156": [1], "430227829": [1], "431089488": [1], "431094957": [1], "431556814": [1], "431733765": [1], "432152079": [1], "432242266": [1], "432363841": [1], "433456376": [1], "433809484": [1], "434278059": [1], "434398892": [1], "434427454": [1], "434444828": [1], "434822223": [1], "434942319": [1], "435046021": [1], "435207681": [1], "435249142": [1], "435348326": [1], "436123953": [1], "436429385": [1], "436437244": [1], "436633962": [1], "437130654": [1], "437242695": [1], "437272109": [1], "437561777": [1], "437602469": [1], "437623452": [1], "437678864": [1], "437816280": [1], "438022019": [1], "438088030": [1], "438282236": [1], "438287702": [1], "439034169": [1], "439208368": [1], "439582631": [1], "439976993": [1], "440014240": [1], "440099126": [1], "440127214": [1], "440414451": [1], "440415719": [1], "440614632": [1], "440718359": [1], "440819893": [1], "440927497": [1], "441197655": [1], "441200033": [1], "441620880": [1], "442310005": [1], "442687203": [1], "442692304": [1], "442779884": [1], "442797172": [1], "443032083": [1], "443309343": [1], "443415095": [1], "443666102": [1], "443770021": [1], "443864726": [1], "444716226": [1], "444921542": [1], "444984220": [1], "445298325": [1], "445452547": [1], "445496621": [1], "445574989": [1], "445916894": [1], "446013573": [1], "446126589": [1], "446421413": [1], "446683569": [1], "446777669": [1], "447493126": [1], "447605110": [1], "447699753": [1], "447903847": [1], "448081483": [1], "448229579": [1], "448258235": [1], "448523556": [1], "448838339": [1], "448924338": [1], "449419786": [1], "449717239": [1], "449841474": [1], "450684404": [1], "451010885": [1], "451554435": [1], "451572939": [1], "451663477": [1], "451739290": [1], "451764220": [1], "451931614": [1], "452174963": [1], "452346254": [1], "452374066": [1], "452414754": [1], "452440895": [1], "452450475": [1], "452857512": [1], "452903060": [1], "453124021": [1], "453425315": [1], "453765575": [1], "453927043": [1], "454384759": [1], "454610868": [1], "454960420": [1], "455627176": [1], "455838968": [1], "455996362": [1], "456121138": [1], "456193554": [1], "456194603": [1], "456246975": [1], "456498538": [1], "456585048": [1], "456824573": [1], "456890016": [1], "457169202": [1], "457251727": [1], "457367950": [1], "457600179": [1], "457609205": [1], "457758410": [1], "457880819": [1], "457985410": [1], "458035802": [1], "458177236": [1], "458508350": [1], "458975192": [1], "459309512": [1], "459545501": [1], "459579031": [1], "460049144": [1], "460094339": [1], "460227714": [1], "460270818": [1], "460912709": [1], "460948726": [1], "461018833": [1], "461064560": [1], "461358278": [1], "461753783": [1], "462442545": [1], "463011100": [1], "463188565": [1], "463408118": [1], "463408297": [1], "463738815": [1], "463933823": [1], "464318515": [1], "464330160": [1], "464435773": [1], "464487653": [1], "464580010": [1], "464811522": [1], "464857574": [1], "464925550": [1], "465191633": [1], "465360220": [1], "465473170": [1], "465474571": [1], "465645724": [1], "466127943": [1], "466546713": [1], "466702513": [1], "466921855": [1], "467395766": [1], "467698787": [1], "468532014": [1], "468649460": [1], "468837948": [1], "469131882": [1], "469170115": [1], "469355326": [1], "469505410": [1], "469526923": [1], "469586539": [1], "469908906": [1], "469961307": [1], "470471672": [1], "470850954": [1], "470882355": [1], "471451884": [1], "471843704": [1], "471878548": [1], "472207943": [1], "472342906": [1], "472592006": [1], "472850543": [1], "473651950": [1], "473866923": [1], "473884638": [1], "474189798": [1], "474605405": [1], "474622969": [1], "474780850": [1], "475374707": [1], "475640370": [1], "475676432": [1], "475965078": [1], "476038570": [1], "476243917": [1], "477149380": [1], "477317005": [1], "477486481": [1], "477564703": [1], "477807947": [1], "477810283": [1], "478286317": [1], "478567971": [1], "478647140": [1], "478786936": [1], "479391271": [1], "479638330": [1], "479809018": [1], "480471765": [1], "480516779": [1], "480520555": [1], "481310968": [1], "483765358": [1], "483858726": [1], "484138946": [1], "484175835": [1], "484234602": [1], "484244960": [1], "484367693": [1], "484381365": [1], "484699798": [1], "485879450": [1], "486174624": [1], "486461606": [1], "486585507": [1], "486604540": [1], "486741690": [1], "487044088": [1], "487141785": [1], "487190365": [1], "487390770": [1], "487515052": [1], "487884354": [1], "487997907": [1], "488135526": [1], "488200660": [1], "488201794": [1], "488571957": [1], "488861804": [1], "488905388": [1], "489125292": [1], "489269942": [1], "489849805": [1], "490022398": [1], "490057379": [1], "490199867": [1], "490240526": [1], "490298292": [1], "490325394": [1], "490525794": [1], "490655532": [1], "490940901": [1], "491011874": [1], "491061572": [1], "491608791": [1], "491899013": [1], "491994525": [1], "492152084": [1], "492176876": [1], "492667729": [1], "492714997": [1], "493799589": [1], "493829622": [1], "494674973": [1], "494689539": [1], "494863664": [1], "494910035": [1], "494926789": [1], "495122520": [1], "495137188": [1], "495348861": [1], "495974299": [1], "496091323": [1], "496287756": [1], "496492836": [1], "496534947": [1], "496808882": [1], "497041317": [1], "497043563": [1], "497192530": [1], "497411658": [1], "497621318": [1], "497925017": [1], "498278891": [1], "498426236": [1], "499145424": [1], "499146467": [1], "499365208": [1], "499502273": [1], "499607570": [1], "499747194": [1], "499783310": [1], "500400992": [1], "500410899": [1], "501082393": [1], "501174878": [1], "501198393": [1], "501321191": [1], "501395693": [1], "501417761": [1], "501687005": [1], "501922585": [1], "502451274": [1], "502993382": [1], "503155584": [1], "503635254": [1], "503759498": [1], "503988660": [1], "503999961": [1], "504191666": [1], "504433338": [1], "504531591": [1], "504943233": [1], "505297526": [1], "505423454": [1], "505781258": [1], "506004449": [1], "506021217": [1], "506095525": [1], "507047306": [1], "507131700": [1], "507512919": [1], "507800350": [1], "507880280": [1], "507881350": [1], "508133226": [1], "508150298": [1], "508156320": [1], "508356929": [1], "508413017": [1], "508791895": [1], "508813314": [1], "508823016": [1], "508892360": [1], "509141241": [1], "509821143": [1], "510191157": [1], "510239641": [1], "511053039": [1], "511428547": [1], "511521534": [1], "511616906": [1], "511713938": [1], "511792455": [1], "511814114": [1], "512779255": [1], "512825863": [1], "512924166": [1], "512930648": [1], "512942856": [1], "513097646": [1], "513920031": [1], "513960896": [1], "513970332": [1], "514108163": [1], "514346238": [1], "514403148": [1], "514436315": [1], "514684569": [1], "514764607": [1], "514775637": [1], "515075819": [1], "515399822": [1], "515495522": [1], "515496807": [1], "515574407": [1], "515583095": [1], "515764080": [1], "515885326": [1], "516433666": [1], "516455312": [1], "516466703": [1], "516649036": [1], "516695126": [1], "516940895": [1], "517281822": [1], "517285489": [1], "517917179": [1], "518146748": [1], "518611666": [1], "518636533": [1], "518937671": [1], "519350549": [1], "519388196": [1], "519690871": [1], "520246004": [1], "521914075": [1], "522235917": [1], "522247360": [1], "522395583": [1], "522519439": [1], "522740100": [1], "522872721": [1], "523009950": [1], "523035192": [1], "523212717": [1], "523291620": [1], "523313573": [1], "523356277": [1], "523457035": [1], "523559316": [1], "524149617": [1], "524429179": [1], "524519706": [1], "524561926": [1], "524882537": [1], "525245038": [1], "525348560": [1], "525650870": [1], "525992547": [1], "526669059": [1], "526693990": [1], "526955120": [1], "527128527": [1], "527147049": [1], "527384619": [1], "527419726": [1], "527538518": [1], "528175556": [1], "529160898": [1], "529192568": [1], "529227858": [1], "529573642": [1], "529582633": [1], "529874054": [1], "529916480": [1], "529985399": [1], "530419815": [1], "530554750": [1], "530671542": [1], "530722811": [1], "531034434": [1], "531147337": [1], "531291769": [1], "531371317": [1], "532047880": [1], "532339281": [1], "532561300": [1], "532838222": [1], "532984460": [1], "533229935": [1], "533419811": [1], "533527730": [1], "533870108": [1], "534076208": [1], "534096612": [1], "534288183": [1], "534622781": [1], "534985396": [1], "535334160": [1], "535391350": [1], "535997397": [1], "536805018": [1], "536858287": [1], "537433267": [1], "537505165": [1], "537672301": [1], "537802512": [1], "538067647": [1], "538165289": [1], "538201744": [1], "539057105": [1], "539586055": [1], "539949567": [1], "540111431": [1], "540415451": [1], "540449122": [1], "540707107": [1], "541112297": [1], "541381286": [1], "541536341": [1], "542530007": [1], "542735653": [1], "542832622": [1], "543092503": [1], "543323826": [1], "543423522": [1], "543820435": [1], "543826409": [1], "544119406": [1], "544223997": [1], "544695151": [1], "544760772": [1], "544878642": [1], "545056325": [1], "545098968": [1], "545228608": [1], "545285448": [1], "545615956": [1], "546366808": [1], "546578209": [1], "547175387": [1], "547489168": [1], "547525678": [1], "547945308": [1], "548100065": [1], "548106348": [1], "548272819": [1], "548657777": [1], "548725826": [1], "548853128": [1], "548915106": [1], "549094808": [1], "549417872": [1], "549520070": [1], "549925845": [1], "550660129": [1], "550882606": [1], "550959524": [1], "550992177": [1], "551072603": [1], "551354543": [1], "551793753": [1], "551803462": [1], "551812202": [1], "551886660": [1], "551897796": [1], "551943276": [1], "552220286": [1], "552421451": [1], "552692475": [1], "552923511": [1], "553067399": [1], "553362197": [1], "554043714": [1], "554452826": [1], "555493585": [1], "555527129": [1], "555711003": [1], "556323335": [1], "556442545": [1], "556772613": [1], "557456091": [1], "557745530": [1], "557790020": [1], "557898494": [1], "557927409": [1], "558068747": [1], "558708320": [1], "558808400": [1], "558812719": [1], "559215869": [1], "559476811": [1], "560371163": [1], "560994900": [1], "561160740": [1], "561205161": [1], "561241381": [1], "561310000": [1], "561728782": [1], "562150982": [1], "562203859": [1], "562554588": [1], "562676257": [1], "562738872": [1], "564082476": [1], "564240179": [1], "564564504": [1], "564574965": [1], "564673119": [1], "564809007": [1], "565268822": [1], "566050028": [1], "566148498": [1], "566479377": [1], "566704912": [1], "566754653": [1], "566820257": [1], "567083185": [1], "567737645": [1], "567777844": [1], "567849128": [1], "568423777": [1], "568556962": [1], "568963271": [1], "569049851": [1], "569182318": [1], "569257791": [1], "569471858": [1], "569617886": [1], "569892132": [1], "570307602": [1], "571449300": [1], "571756566": [1], "572011096": [1], "572403450": [1], "573447201": [1], "574427736": [1], "574826588": [1], "574984197": [1], "575041316": [1], "575352985": [1], "575488674": [1], "575613710": [1], "575745367": [1], "576550049": [1], "576966577": [1], "577428564": [1], "577964611": [1], "578297194": [1], "578297411": [1], "578373462": [1], "578499211": [1], "578618548": [1], "578709786": [1], "578721543": [1], "578746730": [1], "578833524": [1], "578954205": [1], "579092038": [1], "579624297": [1], "579694532": [1], "579923383": [1], "579957468": [1], "580436516": [1], "580841255": [1], "581542537": [1], "582008644": [1], "582151001": [1], "582941167": [1], "583059323": [1], "583278275": [1], "583663471": [1], "583697419": [1], "583747076": [1], "584624132": [1], "585411355": [1], "585573416": [1], "585757093": [1], "585831272": [1], "585895434": [1], "585901385": [1], "586142631": [1], "586479597": [1], "587081465": [1], "587149535": [1], "587867589": [1], "587969367": [1], "588039279": [1], "588913426": [1], "589097075": [1], "589371364": [1], "589593222": [1], "589711949": [1], "589843165": [1], "590030605": [1], "591034498": [1], "591525727": [1], "591662791": [1], "591879516": [1], "592357239": [1], "592789415": [1], "592864298": [1], "593141723": [1], "593279465": [1], "593436307": [1], "593632375": [1], "593802915": [1], "593842312": [1], "594741214": [1], "594853270": [1], "595026760": [1], "595156996": [1], "595157305": [1], "595456751": [1], "596252263": [1], "596672051": [1], "596889454": [1], "596906396": [1], "597032401": [1], "597191232": [1], "597797777": [1], "598061374": [1], "598300081": [1], "598495366": [1], "598710303": [1], "598729586": [1], "599173292": [1], "599718875": [1], "600111699": [1], "600223574": [1], "600500841": [1], "600504358": [1], "600721765": [1], "601077743": [1], "601392460": [1], "601551986": [1], "601648662": [1], "601888029": [1], "602363144": [1], "602526309": [1], "602849812": [1], "602941949": [1], "603301823": [1], "603340800": [1], "603828583": [1], "604069656": [1], "604133462": [1], "604256551": [1], "604437094": [1], "604455994": [1], "604797243": [1], "605073853": [1], "605151926": [1], "605210919": [1], "605462510": [1], "605486405": [1], "605897509": [1], "606084640": [1], "606341607": [1], "606630953": [1], "606759742": [1], "606771528": [1], "606886845": [1], "606925884": [1], "607234973": [1], "607721427": [1], "607870607": [1], "607916691": [1], "608046004": [1], "608492204": [1], "608629466": [1], "609564046": [1], "609665532": [1], "610328625": [1], "610391882": [1], "611118062": [1], "611491020": [1], "611586485": [1], "611677757": [1], "611707495": [1], "611762705": [1], "612019732": [1], "612277055": [1], "612532321": [1], "612657319": [1], "613033386": [1], "613644502": [1], "614181263": [1], "614186499": [1], "614374927": [1], "614482496": [1], "614637371": [1], "614826436": [1], "615030205": [1], "615123105": [1], "615377295": [1], "615422804": [1], "615582502": [1], "615593234": [1], "615652911": [1], "615769453": [1], "615861691": [1], "615889186": [1], "615956902": [1], "615969593": [1], "616055230": [1], "616491412": [1], "616995383": [1], "617500296": [1], "617509244": [1], "617620165": [1], "617689230": [1], "618125337": [1], "618189933": [1], "618730394": [1], "618761908": [1], "618917572": [1], "619179841": [1], "619468062": [1], "619487221": [1], "619778690": [1], "619822089": [1], "620437645": [1], "621215593": [1], "621486227": [1], "621661038": [1], "622082670": [1], "622109719": [1], "622376151": [1], "622449534": [1], "622977929": [1], "623405816": [1], "623617559": [1], "624145372": [1], "624592013": [1], "624597430": [1], "624754141": [1], "624885510": [1], "624973797": [1], "625459193": [1], "625463689": [1], "625674842": [1], "625820976": [1], "626302255": [1], "626329940": [1], "626536462": [1], "626539686": [1], "626755378": [1], "626809140": [1], "626900024": [1], "626935355": [1], "626960266": [1], "627069271": [1], "627094353": [1], "627139518": [1], "627683580": [1], "627741698": [1], "628672370": [1], "628974948": [1], "629060868": [1], "629262328": [1], "629272613": [1], "629275666": [1], "629424263": [1], "629615210": [1], "629784737": [1], "630102764": [1], "630451660": [1], "630867417": [1], "631043355": [1], "631294240": [1], "631646349": [1], "631740118": [1], "631999647": [1], "632214272": [1], "632600767": [1], "634457966": [1], "634697628": [1], "635016370": [1], "635298930": [1], "635541329": [1], "635569103": [1], "635587887": [1], "635904275": [1], "636186250": [1], "636835192": [1], "636967831": [1], "637441288": [1], "637617024": [1], "638030195": [1], "638181743": [1], "638714574": [1], "639048651": [1], "639278742": [1], "639750936": [1], "639973650": [1], "640336458": [1], "640688495": [1], "641103227": [1], "641260743": [1], "641395886": [1], "641413035": [1], "641490494": [1], "641617959": [1], "641778922": [1], "641879101": [1], "642008706": [1], "642177556": [1], "642419086": [1], "642846451": [1], "642942778": [1], "642981967": [1], "643326814": [1], "643682181": [1], "643688560": [1], "644156708": [1], "644723805": [1], "644776225": [1], "644964665": [1], "645141349": [1], "645209981": [1], "645568336": [1], "646102139": [1], "646236358": [1], "646292077": [1], "646487471": [1], "646753078": [1], "646851026": [1], "646917472": [1], "647720086": [1], "648436801": [1], "648493613": [1], "648548799": [1], "648697899": [1], "649032911": [1], "649067981": [1], "649127266": [1], "649336969": [1], "649872342": [1], "649971887": [1], "650311086": [1], "650470943": [1], "650518874": [1], "650565633": [1], "650758049": [1], "650934051": [1], "651077489": [1], "651164611": [1], "651588773": [1], "651884206": [1], "652288498": [1], "653035600": [1], "653309644": [1], "653448663": [1], "653476854": [1], "653768591": [1], "653844547": [1], "653912256": [1], "654287237": [1], "654355198": [1], "654450973": [1], "654706938": [1], "655130596": [1], "655499510": [1], "655591458": [1], "656153830": [1], "656424539": [1], "656555755": [1], "656709579": [1], "656896079": [1], "657054155": [1], "657349680": [1], "657888105": [1], "658091727": [1], "658094698": [1], "658099558": [1], "658300188": [1], "658415009": [1], "658417948": [1], "658457860": [1], "658495576": [1], "658766729": [1], "659374701": [1], "659476939": [1], "659688147": [1], "659735443": [1], "660576392": [1], "660604784": [1], "660795372": [1], "661256487": [1], "661629580": [1], "661832581": [1], "661860388": [1], "662059713": [1], "662113773": [1], "662132933": [1], "662156838": [1], "662297387": [1], "662589420": [1], "662757887": [1], "663070993": [1], "663198393": [1], "663216536": [1], "663244686": [1], "663648825": [1], "663712118": [1], "663743645": [1], "664092560": [1], "664153861": [1], "664606750": [1], "665249343": [1], "665649533": [1], "666080937": [1], "666107247": [1], "666516647": [1], "667064994": [1], "667596850": [1], "667802791": [1], "667814473": [1], "668210611": [1], "668434927": [1], "668488486": [1], "668489705": [1], "668604709": [1], "668634019": [1], "669231167": [1], "669356800": [1], "671134002": [1], "671491141": [1], "671954342": [1], "672104954": [1], "672151013": [1], "672275126": [1], "672323843": [1], "672374736": [1], "672648052": [1], "672664672": [1], "673063123": [1], "673124257": [1], "673162438": [1], "673313674": [1], "673681789": [1], "674109204": [1], "674551840": [1], "675100139": [1], "675358742": [1], "675966819": [1], "675987341": [1], "676168521": [1], "676220238": [1], "676262097": [1], "676470160": [1], "676684288": [1], "678105046": [1], "678449141": [1], "678507926": [1], "678564089": [1], "678702985": [1], "678907533": [1], "680083564": [1], "680140876": [1], "680512868": [1], "680751391": [1], "681081254": [1], "681259971": [1], "681323127": [1], "681480107": [1], "681591041": [1], "682237674": [1], "682669435": [1], "683207859": [1], "683212787": [1], "683218613": [1], "683230351": [1], "683587869": [1], "683848107": [1], "683932541": [1], "684347424": [1], "684423279": [1], "684562024": [1], "684597751": [1], "684624648": [1], "684761503": [1], "685776354": [1], "686316691": [1], "686472689": [1], "686592390": [1], "686731846": [1], "686848870": [1], "687196460": [1], "687519096": [1], "687575478": [1], "687706564": [1], "687736132": [1], "688046915": [1], "688234658": [1], "688263777": [1], "688345391": [1], "688348943": [1], "688723019": [1], "688738656": [1], "689050612": [1], "689063781": [1], "689388559": [1], "689412427": [1], "689542849": [1], "689850223": [1], "689946730": [1], "690772134": [1], "691183449": [1], "691311548": [1], "691589556": [1], "691739287": [1], "691971135": [1], "692219787": [1], "692340729": [1], "692479919": [1], "692484522": [1], "692744224": [1], "693157852": [1], "693559241": [1], "693625665": [1], "693951584": [1], "694874299": [1], "695275674": [1], "695373188": [1], "695392406": [1], "695451593": [1], "695929210": [1], "695953554": [1], "696002128": [1], "696321713": [1], "696328910": [1], "696450838": [1], "696853318": [1], "696995279": [1], "697400698": [1], "697619946": [1], "697668407": [1], "697681069": [1], "697762945": [1], "697766116": [1], "697829339": [1], "697988899": [1], "698081688": [1], "698098769": [1], "698359383": [1], "699067236": [1], "699120376": [1], "699534470": [1], "699655105": [1], "699724170": [1], "700059250": [1], "700414171": [1], "700761714": [1], "701592270": [1], "701627690": [1], "701637799": [1], "701933559": [1], "702014716": [1], "702214722": [1], "702522580": [1], "703029725": [1], "703067116": [1], "703843114": [1], "703972532": [1], "704279420": [1], "704282217": [1], "704299383": [1], "704313938": [1], "704422687": [1], "704427261": [1], "705143780": [1], "705405287": [1], "705522774": [1], "705582746": [1], "705894134": [1], "706163578": [1], "706247581": [1], "706351627": [1], "706513973": [1], "707373027": [1], "707444795": [1], "707837660": [1], "707984181": [1], "708364464": [1], "708386299": [1], "708478266": [1], "708483033": [1], "708546058": [1], "708782533": [1], "709196732": [1], "710016547": [1], "710223172": [1], "710244220": [1], "710483101": [1], "710488896": [1], "710918380": [1], "711054380": [1], "711332305": [1], "711481836": [1], "711486111": [1], "711709750": [1], "711784230": [1], "712138820": [1], "712582212": [1], "712688719": [1], "713595728": [1], "713691196": [1], "714617743": [1], "714970385": [1], "715013814": [1], "715024874": [1], "715134116": [1], "715356629": [1], "715376732": [1], "715578365": [1], "715781185": [1], "716035720": [1], "716042361": [1], "716509057": [1], "716663409": [1], "717072289": [1], "717392271": [1], "717470662": [1], "717764194": [1], "717832433": [1], "717861311": [1], "717939993": [1], "717950008": [1], "718965771": [1], "719237775": [1], "719260634": [1], "719426054": [1], "719750114": [1], "719775037": [1], "719860597": [1], "719966981": [1], "720013622": [1], "720412619": [1], "720445637": [1], "720730170": [1], "720768278": [1], "721445598": [1], "721637235": [1], "721835413": [1], "721902411": [1], "722045881": [1], "722171213": [1], "722200191": [1], "722444541": [1], "722521072": [1], "722759284": [1], "722785546": [1], "723020020": [1], "723286092": [1], "723411560": [1], "723493161": [1], "723527434": [1], "723820403": [1], "724180064": [1], "724297249": [1], "724645152": [1], "724805446": [1], "725471552": [1], "725573488": [1], "725764745": [1], "725901761": [1], "725953250": [1], "726095892": [1], "726243145": [1], "726268335": [1], "726408265": [1], "726507238": [1], "726523192": [1], "726852112": [1], "726855867": [1], "726917289": [1], "727189386": [1], "727294826": [1], "727699411": [1], "727936789": [1], "728135616": [1], "728341849": [1], "728358524": [1], "728849207": [1], "728919212": [1], "728953374": [1], "729028251": [1], "729559904": [1], "729720353": [1], "729812815": [1], "730306908": [1], "730389411": [1], "730650402": [1], "730911138": [1], "731523007": [1], "731580527": [1], "731970196": [1], "732544289": [1], "732789634": [1], "732851011": [1], "733089798": [1], "733126532": [1], "733695530": [1], "733706557": [1], "734338568": [1], "734368632": [1], "734432572": [1], "734686317": [1], "734844569": [1], "734880719": [1], "734888269": [1], "734951859": [1], "735124802": [1], "735675590": [1], "735782836": [1], "735803319": [1], "736053885": [1], "736148607": [1], "736193033": [1], "736330679": [1], "736626016": [1], "736702531": [1], "736713829": [1], "738014301": [1], "738272523": [1], "738287648": [1], "738884980": [1], "739006983": [1], "739203791": [1], "739502014": [1], "739813913": [1], "739855105": [1], "740273448": [1], "740367823": [1], "740709920": [1], "740791680": [1], "741322277": [1], "741882144": [1], "741946357": [1], "742055199": [1], "742286322": [1], "742942584": [1], "743081055": [1], "743102809": [1], "743197265": [1], "743300325": [1], "743324689": [1], "743603414": [1], "743719517": [1], "744408332": [1], "744441737": [1], "744586776": [1], "745286869": [1], "745571452": [1], "746031075": [1], "746276046": [1], "746603236": [1], "747601684": [1], "747724085": [1], "747793050": [1], "748025282": [1], "748588412": [1], "749242723": [1], "749250238": [1], "749407286": [1], "750069311": [1], "750275683": [1], "750629152": [1], "751281342": [1], "751295227": [1], "751302700": [1], "751345215": [1], "751444751": [1], "751553058": [1], "751568406": [1], "752007528": [1], "752206061": [1], "752212193": [1], "752486174": [1], "752497173": [1], "752510861": [1], "752575524": [1], "752983151": [1], "753057783": [1], "753206006": [1], "753604658": [1], "753937786": [1], "754637090": [1], "754762951": [1], "754768776": [1], "754967712": [1], "755059645": [1], "755562713": [1], "755584170": [1], "755677115": [1], "756438582": [1], "756464310": [1], "756938487": [1], "757419176": [1], "757497854": [1], "757537841": [1], "757641310": [1], "757778352": [1], "758899254": [1], "759309455": [1], "759981158": [1], "760319182": [1], "760695137": [1], "761051685": [1], "761245939": [1], "761268448": [1], "761342812": [1], "761676028": [1], "761908341": [1], "762422490": [1], "762598762": [1], "762629216": [1], "762920603": [1], "763294721": [1], "763576049": [1], "763844630": [1], "764462972": [1], "764634212": [1], "764762129": [1], "765241741": [1], "765783088": [1], "765855868": [1], "765911533": [1], "765988779": [1], "766268747": [1], "766655142": [1], "766998798": [1], "768187504": [1], "768389792": [1], "768706831": [1], "768751075": [1], "768925685": [1], "768945251": [1], "769381981": [1], "769580223": [1], "769809736": [1], "769863408": [1], "769982754": [1], "770296427": [1], "770393395": [1], "770849514": [1], "770917537": [1], "770974208": [1], "771264313": [1], "771332371": [1], "771527954": [1], "771630476": [1], "772184651": [1], "772233023": [1], "772288423": [1], "772869232": [1], "773107436": [1], "773107666": [1], "773187618": [1], "773197811": [1], "773886002": [1], "774218089": [1], "774254041": [1], "774586523": [1], "774674508": [1], "774874071": [1], "775185794": [1], "775514368": [1], "775710300": [1], "776200469": [1], "776548794": [1], "776564276": [1], "776628414": [1], "776726142": [1], "777176089": [1], "777221132": [1], "777458294": [1], "777602286": [1], "777781125": [1], "777825649": [1], "778100878": [1], "778249688": [1], "778264686": [1], "778666636": [1], "778899121": [1], "778933619": [1], "779052040": [1], "779304061": [1], "779367898": [1], "779558244": [1], "779824757": [1], "779891326": [1], "780188951": [1], "780433722": [1], "780687805": [1], "780766303": [1], "780830222": [1], "780931550": [1], "781142596": [1], "781177552": [1], "781252185": [1], "781393590": [1], "781580598": [1], "781986974": [1], "782207920": [1], "782243901": [1], "782719814": [1], "782725490": [1], "783046461": [1], "783176162": [1], "783385886": [1], "783657379": [1], "783687212": [1], "783738072": [1], "783841932": [1], "783992652": [1], "784049726": [1], "784448792": [1], "784481875": [1], "785393386": [1], "785537807": [1], "785718307": [1], "785780191": [1], "785900158": [1], "786025949": [1], "786191852": [1], "786484963": [1], "786799243": [1], "786809080": [1], "786824889": [1], "786912849": [1], "787118098": [1], "787353826": [1], "788369240": [1], "788510900": [1], "788880699": [1], "788949860": [1], "789075205": [1], "789335793": [1], "789727083": [1], "789937116": [1], "790270609": [1], "790424983": [1], "790569080": [1], "790953735": [1], "791024072": [1], "791104980": [1], "791180604": [1], "791214929": [1], "791773692": [1], "792004623": [1], "792008085": [1], "793072268": [1], "793077498": [1], "793590835": [1], "793659390": [1], "794042763": [1], "794256296": [1], "794508549": [1], "794905151": [1], "795230301": [1], "795386973": [1], "795839111": [1], "796058544": [1], "796736636": [1], "797232228": [1], "797245099": [1], "797396755": [1], "797646200": [1], "797652463": [1], "797983604": [1], "798413210": [1], "798785365": [1], "798871656": [1], "799013063": [1], "799272531": [1], "799368226": [1], "799417427": [1], "799521325": [1], "799654471": [1], "800240360": [1], "800621183": [1], "801258558": [1], "801678698": [1], "801685465": [1], "801782525": [1], "802145062": [1], "802180798": [1], "802570397": [1], "802720235": [1], "802734290": [1], "802758314": [1], "802857343": [1], "803077503": [1], "803385428": [1], "803609063": [1], "803662203": [1], "803764375": [1], "803765593": [1], "803959903": [1], "804196654": [1], "804347644": [1], "804575221": [1], "804779315": [1], "805210066": [1], "805719717": [1], "805726135": [1], "805812030": [1], "805816179": [1], "805874359": [1], "805892310": [1], "806099542": [1], "806107616": [1], "806514400": [1], "806768313": [1], "806788821": [1], "806903788": [1], "806967376": [1], "807077422": [1], "807126419": [1], "807522065": [1], "808251733": [1], "808431651": [1], "808565575": [1], "808660090": [1], "809938216": [1], "809961601": [1], "810017572": [1], "810338071": [1], "810665679": [1], "810685283": [1], "811412867": [1], "811572795": [1], "811730175": [1], "811976039": [1], "812121711": [1], "812160993": [1], "812608101": [1], "812911589": [1], "813282947": [1], "813524478": [1], "813594665": [1], "813858350": [1], "813878278": [1], "813886430": [1], "814207378": [1], "814292800": [1], "814417111": [1], "814512904": [1], "815158599": [1], "815926670": [1], "816090590": [1], "816266615": [1], "816379312": [1], "816862429": [1], "816963228": [1], "816984119": [1], "817039108": [1], "817191365": [1], "817519083": [1], "817662611": [1], "818014280": [1], "818054004": [1], "818786247": [1], "819107686": [1], "819444882": [1], "820053469": [1], "820086450": [1], "820912597": [1], "821612826": [1], "821940249": [1], "822462153": [1], "823025493": [1], "823510159": [1], "823851616": [1], "824878943": [1], "825479745": [1], "825511765": [1], "825521298": [1], "825783075": [1], "825852827": [1], "825967393": [1], "826011203": [1], "826191490": [1], "826248441": [1], "826833915": [1], "826926348": [1], "827396252": [1], "827516214": [1], "827681786": [1], "827776152": [1], "828133547": [1], "828531872": [1], "828638311": [1], "828722702": [1], "829290662": [1], "829516931": [1], "829658748": [1], "830028931": [1], "830474978": [1], "830503549": [1], "830568262": [1], "830586973": [1], "830638063": [1], "830982746": [1], "831145417": [1], "831542898": [1], "831602316": [1], "831827467": [1], "831920732": [1], "831958642": [1], "832026031": [1], "832092776": [1], "832537703": [1], "832913319": [1], "832959406": [1], "833600270": [1], "833624839": [1], "833842175": [1], "834069207": [1], "834122525": [1], "835853181": [1], "836085409": [1], "836218245": [1], "836421531": [1], "836756878": [1], "836968762": [1], "837290582": [1], "837375556": [1], "837579537": [1], "837745080": [1], "837771638": [1], "837903227": [1], "837952809": [1], "837954444": [1], "838137249": [1], "838451314": [1], "838451563": [1], "838512417": [1], "838643752": [1], "838790339": [1], "839158014": [1], "839416101": [1], "839872285": [1], "840281223": [1], "840604107": [1], "840609087": [1], "840635980": [1], "840782635": [1], "841170457": [1], "841278587": [1], "842152370": [1], "842670119": [1], "842729766": [1], "842757747": [1], "842825330": [1], "842907079": [1], "843255408": [1], "843259811": [1], "843453289": [1], "843588222": [1], "843879276": [1], "844206710": [1], "844265361": [1], "845524080": [1], "845660733": [1], "845943243": [1], "846036074": [1], "846155805": [1], "846495818": [1], "846603276": [1], "846671598": [1], "846790308": [1], "846842459": [1], "846982291": [1], "847031281": [1], "847508267": [1], "848394997": [1], "848404002": [1], "848962773": [1], "849018465": [1], "849239605": [1], "849487025": [1], "849716035": [1], "849837310": [1], "849852881": [1], "850719286": [1], "851205054": [1], "851492317": [1], "851525679": [1], "851841921": [1], "852394644": [1], "852875797": [1], "853024973": [1], "853319030": [1], "853578464": [1], "853621942": [1], "853699321": [1], "853804814": [1], "854188309": [1], "854460722": [1], "854872634": [1], "854947809": [1], "854967800": [1], "855065954": [1], "855162100": [1], "855510808": [1], "855877763": [1], "856156761": [1], "856345220": [1], "856395293": [1], "856652061": [1], "856872796": [1], "856883506": [1], "857087327": [1], "857384333": [1], "857521141": [1], "857924250": [1], "857935550": [1], "857948842": [1], "857959154": [1], "858183016": [1], "858577273": [1], "858712948": [1], "858925443": [1], "859401515": [1], "860611336": [1], "860695888": [1], "860808431": [1], "860857565": [1], "860973389": [1], "861111191": [1], "861244507": [1], "861305547": [1], "861512732": [1], "862061879": [1], "862406812": [1], "862766161": [1], "863013707": [1], "863324531": [1], "863525406": [1], "864078281": [1], "864704908": [1], "864891400": [1], "865003217": [1], "865291063": [1], "865398369": [1], "865414645": [1], "865446219": [1], "865532223": [1], "865802652": [1], "865883087": [1], "866011891": [1], "866817653": [1], "867150841": [1], "867347481": [1], "867737720": [1], "868262352": [1], "868392097": [1], "869134659": [1], "869542467": [1], "869734326": [1], "870451192": [1], "870519866": [1], "870585124": [1], "871147920": [1], "871288316": [1], "871366047": [1], "871716659": [1], "871838156": [1], "872193597": [1], "872320784": [1], "874096105": [1], "874793344": [1], "875789688": [1], "875982276": [1], "876010655": [1], "876264513": [1], "876540404": [1], "876981007": [1], "877168381": [1], "877245743": [1], "877436328": [1], "877473850": [1], "877498604": [1], "878526708": [1], "878758161": [1], "878799543": [1], "879230035": [1], "879748820": [1], "879917339": [1], "879964142": [1], "880036003": [1], "880042977": [1], "880149261": [1], "880195542": [1], "880664583": [1], "880787390": [1], "881397935": [1], "881419928": [1], "881508668": [1], "881717238": [1], "881836407": [1], "882025241": [1], "882060810": [1], "882109638": [1], "882362729": [1], "882547540": [1], "882725440": [1], "882755016": [1], "882856145": [1], "882856501": [1], "882997408": [1], "883111497": [1], "883373112": [1], "883456039": [1], "883559785": [1], "884153907": [1], "884912734": [1], "884937545": [1], "884982586": [1], "885041429": [1], "885147930": [1], "885197192": [1], "885396393": [1], "885421318": [1], "885425467": [1], "885811966": [1], "886113930": [1], "886118547": [1], "886236199": [1], "886278876": [1], "886320515": [1], "886328792": [1], "886461396": [1], "886694042": [1], "886835306": [1], "887160285": [1], "887460767": [1], "887511728": [1], "888053029": [1], "888104118": [1], "888234475": [1], "888451946": [1], "888459734": [1], "888604411": [1], "888753590": [1], "888870100": [1], "888915589": [1], "889426410": [1], "889428235": [1], "889638510": [1], "890293427": [1], "890317908": [1], "890941083": [1], "891334579": [1], "891642082": [1], "891663716": [1], "891827994": [1], "892235667": [1], "892464793": [1], "892523194": [1], "892742555": [1], "892756913": [1], "893181560": [1], "893288448": [1], "893339262": [1], "893342944": [1], "893679532": [1], "893754669": [1], "893885411": [1], "894614812": [1], "894752429": [1], "894978579": [1], "895058638": [1], "895355234": [1], "895669709": [1], "895894650": [1], "896011915": [1], "896112572": [1], "896581783": [1], "896736563": [1], "896880384": [1], "896919090": [1], "897044808": [1], "897390665": [1], "897617907": [1], "898026811": [1], "898576725": [1], "898594385": [1], "898701002": [1], "898791944": [1], "899009930": [1], "899046625": [1], "899470606": [1], "899617756": [1], "899711819": [1], "900089471": [1], "900468408": [1], "901123682": [1], "901132409": [1], "901136606": [1], "901342549": [1], "901416515": [1], "901598317": [1], "901880274": [1], "902211317": [1], "902924315": [1], "902997302": [1], "903365641": [1], "903392904": [1], "903447285": [1], "903609443": [1], "903652761": [1], "903943620": [1], "904269734": [1], "904404557": [1], "905011896": [1], "905355298": [1], "905904458": [1], "906054846": [1], "906103975": [1], "907200545": [1], "907379198": [1], "907481067": [1], "907509658": [1], "907921850": [1], "908165155": [1], "908385836": [1], "908836931": [1], "909180143": [1], "909244874": [1], "909392379": [1], "910231807": [1], "910385493": [1], "910568076": [1], "910588288": [1], "910721583": [1], "911239765": [1], "911974540": [1], "912122106": [1], "912361254": [1], "912616792": [1], "912850857": [1], "913109354": [1], "913120597": [1], "913223206": [1], "913246894": [1], "913382513": [1], "913660654": [1], "913999295": [1], "914022872": [1], "914023861": [1], "914137404": [1], "914228920": [1], "914567182": [1], "914900885": [1], "915209906": [1], "915418269": [1], "915445191": [1], "915597409": [1], "916037124": [1], "916085770": [1], "916189781": [1], "917014038": [1], "917108512": [1], "917350532": [1], "917878782": [1], "917921451": [1], "918295452": [1], "918581835": [1], "919131438": [1], "919321555": [1], "919403600": [1], "919436499": [1], "919565837": [1], "920743731": [1], "920921767": [1], "920959216": [1], "921043311": [1], "921272815": [1], "921524062": [1], "921533295": [1], "921817256": [1], "922098428": [1], "922277728": [1], "922401887": [1], "922787767": [1], "922795070": [1], "923232638": [1], "923269330": [1], "923297798": [1], "923815157": [1], "923929160": [1], "924063280": [1], "924353692": [1], "924492437": [1], "924847210": [1], "925382382": [1], "925515144": [1], "926608505": [1], "926747567": [1], "926761097": [1], "926820080": [1], "927278751": [1], "927746581": [1], "927817144": [1], "928154009": [1], "928286882": [1], "928413318": [1], "928554327": [1], "929446291": [1], "929893448": [1], "930321003": [1], "930470824": [1], "930614776": [1], "930915112": [1], "931508892": [1], "931787765": [1], "932048985": [1], "932128523": [1], "933439351": [1], "933534630": [1], "933641366": [1], "933646942": [1], "934141600": [1], "934205544": [1], "934328994": [1], "934378731": [1], "934607856": [1], "934765266": [1], "934841097": [1], "935194766": [1], "935986174": [1], "936234656": [1], "936284691": [1], "936539477": [1], "936660707": [1], "936730589": [1], "936743209": [1], "937001305": [1], "937140159": [1], "937152628": [1], "937194745": [1], "937478334": [1], "937625401": [1], "937915099": [1], "938097936": [1], "938098537": [1], "938273364": [1], "938802839": [1], "938806982": [1], "938954601": [1], "939035188": [1], "939126581": [1], "939428884": [1], "939480206": [1], "940085430": [1], "940116084": [1], "940285769": [1], "940341346": [1], "940604765": [1], "940771764": [1], "941192736": [1], "941235679": [1], "941949970": [1], "942333729": [1], "942564224": [1], "942584313": [1], "942829354": [1], "943163941": [1], "943266417": [1], "943317895": [1], "943559045": [1], "943756359": [1], "944026129": [1], "944065090": [1], "944120712": [1], "944122112": [1], "944323877": [1], "944753650": [1], "944781741": [1], "944802027": [1], "944830593": [1], "944840018": [1], "944898687": [1], "945071622": [1], "945198637": [1], "945298411": [1], "945302879": [1], "945816135": [1], "945885254": [1], "946001524": [1], "946376788": [1], "946591045": [1], "947012704": [1], "947629769": [1], "948575686": [1], "948936915": [1], "949301415": [1], "949416087": [1], "949444479": [1], "949919741": [1], "949997028": [1], "950199816": [1], "950307048": [1], "950802544": [1], "951117010": [1], "951241870": [1], "951714181": [1], "951838825": [1], "952370915": [1], "952474243": [1], "952981766": [1], "953044970": [1], "953137977": [1], "953253967": [1], "953487989": [1], "953490107": [1], "953674320": [1], "954259671": [1], "954280086": [1], "954525007": [1], "954626985": [1], "954710948": [1], "954802631": [1], "955018779": [1], "955283549": [1], "955299371": [1], "955451406": [1], "955587147": [1], "956055956": [1], "956345343": [1], "956780457": [1], "956805671": [1], "957402676": [1], "957443240": [1], "957744781": [1], "957854914": [1], "957857802": [1], "958162436": [1], "958312482": [1], "958992973": [1], "959048888": [1], "959071605": [1], "959590297": [1], "959676970": [1], "959741308": [1], "959961201": [1], "960133298": [1], "960304770": [1], "960441357": [1], "960666163": [1], "960685585": [1], "961256602": [1], "961299100": [1], "961301307": [1], "961340923": [1], "961914889": [1], "961959337": [1], "962244254": [1], "962818507": [1], "963031485": [1], "963061928": [1], "963404411": [1], "963812284": [1], "964222752": [1], "964428165": [1], "964499709": [1], "964647844": [1], "964698266": [1], "964933540": [1], "965387917": [1], "965508894": [1], "965672665": [1], "965884376": [1], "965908494": [1], "966032692": [1], "966063091": [1], "966147129": [1], "966725465": [1], "966880332": [1], "966909276": [1], "967185235": [1], "967569048": [1], "967615691": [1], "967754017": [1], "968146916": [1], "968241196": [1], "968276128": [1], "968548905": [1], "968756476": [1], "968816463": [1], "968869402": [1], "969335372": [1], "969396601": [1], "969650911": [1], "969656210": [1], "969801720": [1], "969996636": [1], "970314937": [1], "970415704": [1], "970802453": [1], "970817240": [1], "971175167": [1], "971730552": [1], "972388009": [1], "973318462": [1], "973494647": [1], "973573888": [1], "974364520": [1], "974403971": [1], "975043726": [1], "975115858": [1], "975118031": [1], "975621683": [1], "975730421": [1], "975737393": [1], "975765982": [1], "975848256": [1], "976503847": [1], "976519794": [1], "976566553": [1], "976659894": [1], "976719110": [1], "977141603": [1], "977315245": [1], "977437241": [1], "977447015": [1], "977448777": [1], "977513509": [1], "977798631": [1], "977818547": [1], "977972425": [1], "978169124": [1], "978335746": [1], "978550729": [1], "979039943": [1], "979113270": [1], "979166043": [1], "979267797": [1], "979367437": [1], "979610967": [1], "980010607": [1], "980544652": [1], "980697947": [1], "980924318": [1], "981024428": [1], "981087316": [1], "981196981": [1], "981295835": [1], "981494918": [1], "981599875": [1], "981849756": [1], "982056286": [1], "982244983": [1], "982312106": [1], "982403346": [1], "982602841": [1], "982654955": [1], "982664133": [1], "982791147": [1], "982853950": [1], "983034236": [1], "983319577": [1], "983663840": [1], "984061593": [1], "984885189": [1], "985104549": [1], "985117123": [1], "985159252": [1], "985770733": [1], "985927310": [1], "986124261": [1], "986186455": [1], "986241184": [1], "986395005": [1], "986403109": [1], "986796049": [1], "986833768": [1], "986910810": [1], "987134942": [1], "987546315": [1], "987549715": [1], "987684155": [1], "987724032": [1], "988466668": [1], "988804556": [1], "989268580": [1], "989528116": [1], "989560954": [1], "990042915": [1], "990498451": [1], "990746755": [1], "990954446": [1], "991167316": [1], "991532887": [1], "991622130": [1], "992302690": [1], "992628437": [1], "993319277": [1], "993899521": [1], "994245700": [1], "994270321": [1], "994405106": [1], "994646080": [1], "994984235": [1], "995040173": [1], "995085251": [1], "995258438": [1], "995581613": [1], "995624836": [1], "995669039": [1], "995818747": [1], "995939344": [1], "996194924": [1], "996272183": [1], "996714803": [1], "997198239": [1], "997256303": [1], "997391425": [1], "997751197": [1], "997879342": [1], "998029632": [1], "998097780": [1], "998123036": [1], "998231766": [1], "998249909": [1], "998279016": [1], "998317397": [1], "998450541": [1], "998752638": [1], "999165378": [1], "999252300": [1], "999682150": [1], "999704320": [1], "999809974": [1], "1000079561": [1], "1000366900": [1], "1000571373": [1], "1000827620": [1], "1001011683": [1], "1001210108": [1], "1001503649": [1], "1002011849": [1], "1002189244": [1], "1002563995": [1], "1002697086": [1], "1002752266": [1], "1002786749": [1], "1002833158": [1], "1003076362": [1], "1003351052": [1], "1003527286": [1], "1003758904": [1], "1004486581": [1], "1004837010": [1], "1004916234": [1], "1005217243": [1], "1005364034": [1], "1005625269": [1], "1005844943": [1], "1005919962": [1], "1005962627": [1], "1006072365": [1], "1006139903": [1], "1006229148": [1], "1006348952": [1], "1006537327": [1], "1006665423": [1], "1006896575": [1], "1006926316": [1], "1006929683": [1], "1007108192": [1], "1007660887": [1], "1007681986": [1], "1007967966": [1], "1008034859": [1], "1008178843": [1], "1008415388": [1], "1008516488": [1], "1008637857": [1], "1008750195": [1], "1008819962": [1], "1008924384": [1], "1009268094": [1], "1009782721": [1], "1009958773": [1], "1010047646": [1], "1010918588": [1], "1011108133": [1], "1011116779": [1], "1011359929": [1], "1011634750": [1], "1012096578": [1], "1012860497": [1], "1012924543": [1], "1013107240": [1], "1013134551": [1], "1013656155": [1], "1014038394": [1], "1014128900": [1], "1014534298": [1], "1014633391": [1], "1014853913": [1], "1015022918": [1], "1015327850": [1], "1016270799": [1], "1016415776": [1], "1016479200": [1], "1016496489": [1], "1016539598": [1], "1016891393": [1], "1018070636": [1], "1018164695": [1], "1018165511": [1], "1018354841": [1], "1018435689": [1], "1018497572": [1], "1018888494": [1], "1018954738": [1], "1019415874": [1], "1019434959": [1], "1019673535": [1], "1019956044": [1], "1020527112": [1], "1021215205": [1], "1021854983": [1], "1021870062": [1], "1022085821": [1], "1022159013": [1], "1022852646": [1], "1023129724": [1], "1023498499": [1], "1023518731": [1], "1023830199": [1], "1023900315": [1], "1024003549": [1], "1024173300": [1], "1024450047": [1], "1024781192": [1], "1025610387": [1], "1025652801": [1], "1026475135": [1], "1026815889": [1], "1026933342": [1], "1027113773": [1], "1027482992": [1], "1027501958": [1], "1027527027": [1], "1027585441": [1], "1027667032": [1], "1028004658": [1], "1028076357": [1], "1028248260": [1], "1028251887": [1], "1028702830": [1], "1028975962": [1], "1029180681": [1], "1029383313": [1], "1029429555": [1], "1030043329": [1], "1030358470": [1], "1030407819": [1], "1030520202": [1], "1030795523": [1], "1030989918": [1], "1032110913": [1], "1032232585": [1], "1032767580": [1], "1032796929": [1], "1032813818": [1], "1033218370": [1], "1033373858": [1], "1033695276": [1], "1033818877": [1], "1034003475": [1], "1034291973": [1], "1035188742": [1], "1035854366": [1], "1036004499": [1], "1036086831": [1], "1036359076": [1], "1036508759": [1], "1036789743": [1], "1036842862": [1], "1036852806": [1], "1037185234": [1], "1037474923": [1], "1037716849": [1], "1038571609": [1], "1038852032": [1], "1039026172": [1], "1039110643": [1], "1039229148": [1], "1039316340": [1], "1039396388": [1], "1039708016": [1], "1040786256": [1], "1040839866": [1], "1040895866": [1], "1041201256": [1], "1041251080": [1], "1041403193": [1], "1041859516": [1], "1041975061": [1], "1042093419": [1], "1042111762": [1], "1042534892": [1], "1042742588": [1], "1043007717": [1], "1043167974": [1], "1043169427": [1], "1043183418": [1], "1043294316": [1], "1043948666": [1], "1044198164": [1], "1044853227": [1], "1045130985": [1], "1045363957": [1], "1045555803": [1], "1045667160": [1], "1045802619": [1], "1046014014": [1], "1046132549": [1], "1046360241": [1], "1047185952": [1], "1047320657": [1], "1047557699": [1], "1047573233": [1], "1047596960": [1], "1048412282": [1], "1048468808": [1], "1048938275": [1], "1048950484": [1], "1049546274": [1], "1049587019": [1], "1049874144": [1], "1050100265": [1], "1050432509": [1], "1050478771": [1], "1050802655": [1], "1051040440": [1], "1051946328": [1], "1052380750": [1], "1052459761": [1], "1052794649": [1], "1053611574": [1], "1053682131": [1], "1053721972": [1], "1053817294": [1], "1053832810": [1], "1053890379": [1], "1054104729": [1], "1054174289": [1], "1054317911": [1], "1054710619": [1], "1054886738": [1], "1055370419": [1], "1055385375": [1], "1055411886": [1], "1055492225": [1], "1055895040": [1], "1056363928": [1], "1056553688": [1], "1056866320": [1], "1057070677": [1], "1057240205": [1], "1057376023": [1], "1057601590": [1], "1058297435": [1], "1058542921": [1], "1059045823": [1], "1059426804": [1], "1059431384": [1], "1059452982": [1], "1059571518": [1], "1060103327": [1], "1060202981": [1], "1060341291": [1], "1060923444": [1], "1061258934": [1], "1061517690": [1], "1061825913": [1], "1061831044": [1], "1062373877": [1], "1062944418": [1], "1063029466": [1], "1063365622": [1], "1063367088": [1], "1063974508": [1], "1064235646": [1], "1064321894": [1], "1064404393": [1], "1064588366": [1], "1064733223": [1], "1065087848": [1], "1065423459": [1], "1065430484": [1], "1066090930": [1], "1066172740": [1], "1066407686": [1], "1066435785": [1], "1066572859": [1], "1066720090": [1], "1066864807": [1], "1067009528": [1], "1067154864": [1], "1067422557": [1], "1067681587": [1], "1067702270": [1], "1068519399": [1], "1068546185": [1], "1068634919": [1], "1069053808": [1], "1069448304": [1], "1069670921": [1], "1069988909": [1], "1070164374": [1], "1070394782": [1], "1070807954": [1], "1071002338": [1], "1071081588": [1], "1071158098": [1], "1071325624": [1], "1071568326": [1], "1071694575": [1], "1071716888": [1], "1071964165": [1], "1072242024": [1], "1072577361": [1], "1072714154": [1], "1072727465": [1], "1072762008": [1], "1072844722": [1], "1073094114": [1], "1073486259": [1], "1073577626": [1], "1073642709": [1], "1073850611": [1], "1073905007": [1], "1074187281": [1], "1074861290": [1], "1074880973": [1], "1074986451": [1], "1075053359": [1], "1075296279": [1], "1075704259": [1], "1075709296": [1], "1076055667": [1], "1076923665": [1], "1076951673": [1], "1076984532": [1], "1077356550": [1], "1077550483": [1], "1077665522": [1], "1077862741": [1], "1078191387": [1], "1078270481": [1], "1078719785": [1], "1078750156": [1], "1079438170": [1], "1079617885": [1], "1080387837": [1], "1080391363": [1], "1080951863": [1], "1081366530": [1], "1081591675": [1], "1081713033": [1], "1081966337": [1], "1082047295": [1], "1082082955": [1], "1082256683": [1], "1082430176": [1], "1082487957": [1], "1082607356": [1], "1083158706": [1], "1083300464": [1], "1083564081": [1], "1083680163": [1], "1084135364": [1], "1084437481": [1], "1084514756": [1], "1084609317": [1], "1084866026": [1], "1084898066": [1], "1085042712": [1], "1085047505": [1], "1085092613": [1], "1085240074": [1], "1085367019": [1], "1085552078": [1], "1085986160": [1], "1086051996": [1], "1086127892": [1], "1086138570": [1], "1086539892": [1], "1086543603": [1], "1086662834": [1], "1086870704": [1], "1087286626": [1], "1087810447": [1], "1088372936": [1], "1088402394": [1], "1088542916": [1], "1089754870": [1], "1089804773": [1], "1090004336": [1], "1090144307": [1], "1090198947": [1], "1090511012": [1], "1090513568": [1], "1090774860": [1], "1090793131": [1], "1090815246": [1], "1091061182": [1], "1091112664": [1], "1091580324": [1], "1091847577": [1], "1091861486": [1], "1091929792": [1], "1092029809": [1], "1092090629": [1], "1092221482": [1], "1092275711": [1], "1092284902": [1], "1092559027": [1], "1092821091": [1], "1093359474": [1], "1093638083": [1], "1094078537": [1], "1094124038": [1], "1094262958": [1], "1094304817": [1], "1094431188": [1], "1095110812": [1], "1095443414": [1], "1095726424": [1], "1096254857": [1], "1096749154": [1], "1097411397": [1], "1098006172": [1], "1098025108": [1], "1098411222": [1], "1099216362": [1], "1099439025": [1], "1099788543": [1], "1099822835": [1], "1100443036": [1], "1100491100": [1], "1100679939": [1], "1101113163": [1], "1101128614": [1], "1101409782": [1], "1101524512": [1], "1101683044": [1], "1101783995": [1], "1101858389": [1], "1102007971": [1], "1102311749": [1], "1102730669": [1], "1103132892": [1], "1103475917": [1], "1103525755": [1], "1103839741": [1], "1104126552": [1], "1104672952": [1], "1105203605": [1], "1105486629": [1], "1105509475": [1], "1105598992": [1], "1105732553": [1], "1105848254": [1], "1105925468": [1], "1105982683": [1], "1106304142": [1], "1106819729": [1], "1107643615": [1], "1108056308": [1], "1108412745": [1], "1108596720": [1], "1108695618": [1], "1108966078": [1], "1109086065": [1], "1109127146": [1], "1109239254": [1], "1109423006": [1], "1109484233": [1], "1109795113": [1], "1110164711": [1], "1110514725": [1], "1110911870": [1], "1111279210": [1], "1111817787": [1], "1112041602": [1], "1112729766": [1], "1113045537": [1], "1113305234": [1], "1113445507": [1], "1113707859": [1], "1114137802": [1], "1114458522": [1], "1114644625": [1], "1115030399": [1], "1115059464": [1], "1115421882": [1], "1115906954": [1], "1116126552": [1], "1116999251": [1], "1117145160": [1], "1117373297": [1], "1118075767": [1], "1118293063": [1], "1119526310": [1], "1120029658": [1], "1120147373": [1], "1120251456": [1], "1120341802": [1], "1120548371": [1], "1120575524": [1], "1120985717": [1], "1121048289": [1], "1121159469": [1], "1121220482": [1], "1121408892": [1], "1121617380": [1], "1121791461": [1], "1121960773": [1], "1122359423": [1], "1122878866": [1], "1122924898": [1], "1123917069": [1], "1124119989": [1], "1124350769": [1], "1124352927": [1], "1124483266": [1], "1124837929": [1], "1124929490": [1], "1125706064": [1], "1125706743": [1], "1125976033": [1], "1125999877": [1], "1126160258": [1], "1126192158": [1], "1126584231": [1], "1127198036": [1], "1127235794": [1], "1127588055": [1], "1127611289": [1], "1127879418": [1], "1127950315": [1], "1128005036": [1], "1128020377": [1], "1128085233": [1], "1128176761": [1], "1128226802": [1], "1128376934": [1], "1128754909": [1], "1129431690": [1], "1129458081": [1], "1129987063": [1], "1130307376": [1], "1130356053": [1], "1130365150": [1], "1130401014": [1], "1130563068": [1], "1131502184": [1], "1132417976": [1], "1133236642": [1], "1133649346": [1], "1133832653": [1], "1133929612": [1], "1133929953": [1], "1133932738": [1], "1133947675": [1], "1134355480": [1], "1134609904": [1], "1134623655": [1], "1134920263": [1], "1135538986": [1], "1135560302": [1], "1136134328": [1], "1136234726": [1], "1136366033": [1], "1137083345": [1], "1137112223": [1], "1137396776": [1], "1138201305": [1], "1138385639": [1], "1138552385": [1], "1138776284": [1], "1138874674": [1], "1138887058": [1], "1138937556": [1], "1139562690": [1], "1140034792": [1], "1140724723": [1], "1140943039": [1], "1140957924": [1], "1141183680": [1], "1141293413": [1], "1141793120": [1], "1142240607": [1], "1142867872": [1], "1143006264": [1], "1143650233": [1], "1143917535": [1], "1144838783": [1], "1144906516": [1], "1145318260": [1], "1145809025": [1], "1145907516": [1], "1146218866": [1], "1146400146": [1], "1146632053": [1], "1146733037": [1], "1147005733": [1], "1147181774": [1], "1147626491": [1], "1147818272": [1], "1148016917": [1], "1148515517": [1], "1148667308": [1], "1148874283": [1], "1149345974": [1], "1149774048": [1], "1149991512": [1], "1150111451": [1], "1150166839": [1], "1150371362": [1], "1150928862": [1], "1151652849": [1], "1151662491": [1], "1152279649": [1], "1152548584": [1], "1152648359": [1], "1152820145": [1], "1152872626": [1], "1152878889": [1], "1153127135": [1], "1153305010": [1], "1153338228": [1], "1153616180": [1], "1154324913": [1], "1154893414": [1], "1155447253": [1], "1155614019": [1], "1155660685": [1], "1155848561": [1], "1156260982": [1], "1156439637": [1], "1157147916": [1], "1157343239": [1], "1157985367": [1], "1158084031": [1], "1158156131": [1], "1158268716": [1], "1158750531": [1], "1158853874": [1], "1159335315": [1], "1159360321": [1], "1160033284": [1], "1160481108": [1], "1160667608": [1], "1161213974": [1], "1161301381": [1], "1161868820": [1], "1162067450": [1], "1162258249": [1], "1162408506": [1], "1162563649": [1], "1162924054": [1], "1162955570": [1], "1163165927": [1], "1163297506": [1], "1163412535": [1], "1163417925": [1], "1163592522": [1], "1163874341": [1], "1163932395": [1], "1164227915": [1], "1164321550": [1], "1165062755": [1], "1165083448": [1], "1165091766": [1], "1165371966": [1], "1165714501": [1], "1166044593": [1], "1166292473": [1], "1166344748": [1], "1166499173": [1], "1166913144": [1], "1167383361": [1], "1167411416": [1], "1167413233": [1], "1167481708": [1], "1167518761": [1], "1167609515": [1], "1167645761": [1], "1167690169": [1], "1167721958": [1], "1168292217": [1], "1168509664": [1], "1169410445": [1], "1169612907": [1], "1169663922": [1], "1170586927": [1], "1170721286": [1], "1171192346": [1], "1171467194": [1], "1171553141": [1], "1171587200": [1], "1171669309": [1], "1171714917": [1], "1172018725": [1], "1172281501": [1], "1172338452": [1], "1173559083": [1], "1173945357": [1], "1174101221": [1], "1174123826": [1], "1174172999": [1], "1174198380": [1], "1174373876": [1], "1174457560": [1], "1174774517": [1], "1174849406": [1], "1175059681": [1], "1175129762": [1], "1175239486": [1], "1175263674": [1], "1175293043": [1], "1175648787": [1], "1175842439": [1], "1175905034": [1], "1176023661": [1], "1176239963": [1], "1176439626": [1], "1176461012": [1], "1176685421": [1], "1176924145": [1], "1177039856": [1], "1177153418": [1], "1177459106": [1], "1177754436": [1], "1177818806": [1], "1177933451": [1], "1177982385": [1], "1178025166": [1], "1178180329": [1], "1178315474": [1], "1178327659": [1], "1178703414": [1], "1178763929": [1], "1178783515": [1], "1178972195": [1], "1179189479": [1], "1179222963": [1], "1179539186": [1], "1180064619": [1], "1180161259": [1], "1180190279": [1], "1180450723": [1], "1180880438": [1], "1181301668": [1], "1181470216": [1], "1181473360": [1], "1182902045": [1], "1183100507": [1], "1183149887": [1], "1183371708": [1], "1183382516": [1], "1183726071": [1], "1183932024": [1], "1184076212": [1], "1184254161": [1], "1184349400": [1], "1184416841": [1], "1184609538": [1], "1185111714": [1], "1185274227": [1], "1185275189": [1], "1185447772": [1], "1185567215": [1], "1185783226": [1], "1186033025": [1], "1186650216": [1], "1186675843": [1], "1186785326": [1], "1186843233": [1], "1187079949": [1], "1187185908": [1], "1188030493": [1], "1188082812": [1], "1188633905": [1], "1188649791": [1], "1188924100": [1], "1189584525": [1], "1189614449": [1], "1189770285": [1], "1190184536": [1], "1190188725": [1], "1190391274": [1], "1190645300": [1], "1190768226": [1], "1191820849": [1], "1191824714": [1], "1191971796": [1], "1191993250": [1], "1192123157": [1], "1192256680": [1], "1192256922": [1], "1192261312": [1], "1192393299": [1], "1192763703": [1], "1193331796": [1], "1193726951": [1], "1193891464": [1], "1194971903": [1], "1195010866": [1], "1195249115": [1], "1196054610": [1], "1196361742": [1], "1196564717": [1], "1196620137": [1], "1196652219": [1], "1197338955": [1], "1197385605": [1], "1197425816": [1], "1197774278": [1], "1197780336": [1], "1198123089": [1], "1198496388": [1], "1198617412": [1], "1198709847": [1], "1199375832": [1], "1199465038": [1], "1199628898": [1], "1199676181": [1], "1200481655": [1], "1200900412": [1], "1201394151": [1], "1202267731": [1], "1202271376": [1], "1202497689": [1], "1203646552": [1], "1203668741": [1], "1203914949": [1], "1204437756": [1], "1204482404": [1], "1204652313": [1], "1204984581": [1], "1205696246": [1], "1205849019": [1], "1205897795": [1], "1206031112": [1], "1206192623": [1], "1207009782": [1], "1207289939": [1], "1207988017": [1], "1208028910": [1], "1208078876": [1], "1209536917": [1], "1209551549": [1], "1209852058": [1], "1210034958": [1], "1210138494": [1], "1210232881": [1], "1210281399": [1], "1210374787": [1], "1210938234": [1], "1211626880": [1], "1212164032": [1], "1212227798": [1], "1212439983": [1], "1212560936": [1], "1212866093": [1], "1213157352": [1], "1213384074": [1], "1213530995": [1], "1213584947": [1], "1213648582": [1], "1214277573": [1], "1214347702": [1], "1214464240": [1], "1214500054": [1], "1214551033": [1], "1214600036": [1], "1214920884": [1], "1215031844": [1], "1215217158": [1], "1215479155": [1], "1215912623": [1], "1216059966": [1], "1216264332": [1], "1216585378": [1], "1216715205": [1], "1216791233": [1], "1217002448": [1], "1217314520": [1], "1217545573": [1], "1217791525": [1], "1217915740": [1], "1217948228": [1], "1218020918": [1], "1218111620": [1], "1218159114": [1], "1218415929": [1], "1218488460": [1], "1218564544": [1], "1218732467": [1], "1219176229": [1], "1219256843": [1], "1219504459": [1], "1219662173": [1], "1219738967": [1], "1219757692": [1], "1220063863": [1], "1220390201": [1], "1220639951": [1], "1221099816": [1], "1221277746": [1], "1221666148": [1], "1222134366": [1], "1222493520": [1], "1222609499": [1], "1223536081": [1], "1224080658": [1], "1224334167": [1], "1224777151": [1], "1225851340": [1], "1226194696": [1], "1226298600": [1], "1226659924": [1], "1226743179": [1], "1226778119": [1], "1226849292": [1], "1227234571": [1], "1227274959": [1], "1227641001": [1], "1227816114": [1], "1228069468": [1], "1228428440": [1], "1228430120": [1], "1228739497": [1], "1229229785": [1], "1229486746": [1], "1229581220": [1], "1229683900": [1], "1229796794": [1], "1230055736": [1], "1230179076": [1], "1230690446": [1], "1230802814": [1], "1230896358": [1], "1231328293": [1], "1231596722": [1], "1231956922": [1], "1232147510": [1], "1232405995": [1], "1232574933": [1], "1232728840": [1], "1232970776": [1], "1233232829": [1], "1233388017": [1], "1233399822": [1], "1234138010": [1], "1234157267": [1], "1234292103": [1], "1234693119": [1], "1235086931": [1], "1235141099": [1], "1235290894": [1], "1235610423": [1], "1235619554": [1], "1235697419": [1], "1235839001": [1], "1236411833": [1], "1237141884": [1], "1237338305": [1], "1237610404": [1], "1237662341": [1], "1237815116": [1], "1238563307": [1], "1238879111": [1], "1238881495": [1], "1238901933": [1], "1239084984": [1], "1239189160": [1], "1239248389": [1], "1239499085": [1], "1239555060": [1], "1239800097": [1], "1239901476": [1], "1239904956": [1], "1240010385": [1], "1240118481": [1], "1240993505": [1], "1241448615": [1], "1242059726": [1], "1242308422": [1], "1242405483": [1], "1242564725": [1], "1242597041": [1], "1242938411": [1], "1243015611": [1], "1243149971": [1], "1243166013": [1], "1243170529": [1], "1243374992": [1], "1243541186": [1], "1244153079": [1], "1244306767": [1], "1244476084": [1], "1244641145": [1], "1244753781": [1], "1244813697": [1], "1244870296": [1], "1245100900": [1], "1245356301": [1], "1245410477": [1], "1245550866": [1], "1245652329": [1], "1245712890": [1], "1245937622": [1], "1246148040": [1], "1246164989": [1], "1246185713": [1], "1246507704": [1], "1246528924": [1], "1247367725": [1], "1247499975": [1], "1247660611": [1], "1247868627": [1], "1248484450": [1], "1248661794": [1], "1248842981": [1], "1249120970": [1], "1249302948": [1], "1249588457": [1], "1249596757": [1], "1249599168": [1], "1249639591": [1], "1250344196": [1], "1250380110": [1], "1250407248": [1], "1250430567": [1], "1250484477": [1], "1250642439": [1], "1250724163": [1], "1250801339": [1], "1250846862": [1], "1250983260": [1], "1251295876": [1], "1251761741": [1], "1251976981": [1], "1252032466": [1], "1252442684": [1], "1252529591": [1], "1252854485": [1], "1253452141": [1], "1253484239": [1], "1253521694": [1], "1254605636": [1], "1255247880": [1], "1255653107": [1], "1256108398": [1], "1256367707": [1], "1256396373": [1], "1256582540": [1], "1256765535": [1], "1257056898": [1], "1257199362": [1], "1257327898": [1], "1257403684": [1], "1257922229": [1], "1257925827": [1], "1257975516": [1], "1258039171": [1], "1258217038": [1], "1258229639": [1], "1258410394": [1], "1258841077": [1], "1259486339": [1], "1259529331": [1], "1260255645": [1], "1260420285": [1], "1261295872": [1], "1261345370": [1], "1261473355": [1], "1261477492": [1], "1261719277": [1], "1261719527": [1], "1262376088": [1], "1262700658": [1], "1263253853": [1], "1263483513": [1], "1264152828": [1], "1264494418": [1], "1264652808": [1], "1265121511": [1], "1265350763": [1], "1265490277": [1], "1265852257": [1], "1265909994": [1], "1266253619": [1], "1266358035": [1], "1267364652": [1], "1267733249": [1], "1267792449": [1], "1267869371": [1], "1268018882": [1], "1268508876": [1], "1268754947": [1], "1269664307": [1], "1269822177": [1], "1270304570": [1], "1270305594": [1], "1271160483": [1], "1272566724": [1], "1272611315": [1], "1272993144": [1], "1273427274": [1], "1273469673": [1], "1273719045": [1], "1273934596": [1], "1273939171": [1], "1273965722": [1], "1274436732": [1], "1274454102": [1], "1274789696": [1], "1275017314": [1], "1275427444": [1], "1275495415": [1], "1275790906": [1], "1276022250": [1], "1276109587": [1], "1276636924": [1], "1276858632": [1], "1277256893": [1], "1277858674": [1], "1278313358": [1], "1278430882": [1], "1278550928": [1], "1278634435": [1], "1278822073": [1], "1279106246": [1], "1279231995": [1], "1279650561": [1], "1279837838": [1], "1279985497": [1], "1280008745": [1], "1280030740": [1], "1280090560": [1], "1280369982": [1], "1280504849": [1], "1280634126": [1], "1281053846": [1], "1281162510": [1], "1281422981": [1], "1281676671": [1], "1281933780": [1], "1282179637": [1], "1282355042": [1], "1283114530": [1], "1283732064": [1], "1284204014": [1], "1284345986": [1], "1284437559": [1], "1285077980": [1], "1285444005": [1], "1285938725": [1], "1286214548": [1], "1286249407": [1], "1287076252": [1], "1287252763": [1], "1288139639": [1], "1288144775": [1], "1289457915": [1], "1289638016": [1], "1289876180": [1], "1290447610": [1], "1290530912": [1], "1290738887": [1], "1290787892": [1], "1290790727": [1], "1291135356": [1], "1291447032": [1], "1291838308": [1], "1292077123": [1], "1292078581": [1], "1292176875": [1], "1292415476": [1], "1292615001": [1], "1292879692": [1], "1292912368": [1], "1293000174": [1], "1293007202": [1], "1293011057": [1], "1293442369": [1], "1293442646": [1], "1293474660": [1], "1293536134": [1], "1293767009": [1], "1293778919": [1], "1293847124": [1], "1293878517": [1], "1294214116": [1], "1294347454": [1], "1294503600": [1], "1295372141": [1], "1296730697": [1], "1297249895": [1], "1297412606": [1], "1297956062": [1], "1298237582": [1], "1298477357": [1], "1298722447": [1], "1298955552": [1], "1299309895": [1], "1299368855": [1], "1299420130": [1], "1299662240": [1], "1299705044": [1], "1299903033": [1], "1300034957": [1], "1300439508": [1], "1300441713": [1], "1300984642": [1], "1301269925": [1], "1301439053": [1], "1301867598": [1], "1301911729": [1], "1302079850": [1], "1302360174": [1], "1302410658": [1], "1302743992": [1], "1302775098": [1], "1303393831": [1], "1303458196": [1], "1303698172": [1], "1304767080": [1], "1304786244": [1], "1305135710": [1], "1305688463": [1], "1305931985": [1], "1305933193": [1], "1306261144": [1], "1306607638": [1], "1307303483": [1], "1307892961": [1], "1307901484": [1], "1308028220": [1], "1308550599": [1], "1308737620": [1], "1308789697": [1], "1309500056": [1], "1309515311": [1], "1309648045": [1], "1309751717": [1], "1309853891": [1], "1309918749": [1], "1310141162": [1], "1310221405": [1], "1311827589": [1], "1311911997": [1], "1312142288": [1], "1312657585": [1], "1312699968": [1], "1312781661": [1], "1312819439": [1], "1312862529": [1], "1313068140": [1], "1313114431": [1], "1313328787": [1], "1313343439": [1], "1313415139": [1], "1313619400": [1], "1314208068": [1], "1314516438": [1], "1314894113": [1], "1314903495": [1], "1314991033": [1], "1315074037": [1], "1315190231": [1], "1315556444": [1], "1315675228": [1], "1315884105": [1], "1316214682": [1], "1316563708": [1], "1316935190": [1], "1317696494": [1], "1317808195": [1], "1317964455": [1], "1318009131": [1], "1318387045": [1], "1318484974": [1], "1318816358": [1], "1318852449": [1], "1318956121": [1], "1319848907": [1], "1319921099": [1], "1320046176": [1], "1320185086": [1], "1320278084": [1], "1320295119": [1], "1320372914": [1], "1320577109": [1], "1320885236": [1], "1320968680": [1], "1322132192": [1], "1322198190": [1], "1322488212": [1], "1322581527": [1], "1322634862": [1], "1323269637": [1], "1323742518": [1], "1323950066": [1], "1324151221": [1], "1324406999": [1], "1324654425": [1], "1325493047": [1], "1325698341": [1], "1326021146": [1], "1326172372": [1], "1326362470": [1], "1326449086": [1], "1326692597": [1], "1326758989": [1], "1326916979": [1], "1327047162": [1], "1327162078": [1], "1327172841": [1], "1327258200": [1], "1327796688": [1], "1328012679": [1], "1328115985": [1], "1329131057": [1], "1329131254": [1], "1329509829": [1], "1329791375": [1], "1330062390": [1], "1330070783": [1], "1330276682": [1], "1330279586": [1], "1330375794": [1], "1330501765": [1], "1330548103": [1], "1330551755": [1], "1330635616": [1], "1330766700": [1], "1331193456": [1], "1331414303": [1], "1331460033": [1], "1331804365": [1], "1332050488": [1], "1332168057": [1], "1332360162": [1], "1332389761": [1], "1332416726": [1], "1333133516": [1], "1333524057": [1], "1333581265": [1], "1333705673": [1], "1333742860": [1], "1333959746": [1], "1333995893": [1], "1334101455": [1], "1334141070": [1], "1334369155": [1], "1334695463": [1], "1335007348": [1], "1335048390": [1], "1335286925": [1], "1335334977": [1], "1335505674": [1], "1335754958": [1], "1336530418": [1], "1336883668": [1], "1337075215": [1], "1337551375": [1], "1337607239": [1], "1337615107": [1], "1337852202": [1], "1338015532": [1], "1338086415": [1], "1338281136": [1], "1338356700": [1], "1338603553": [1], "1339127260": [1], "1339195070": [1], "1339444651": [1], "1339515559": [1], "1339594101": [1], "1339642975": [1], "1339893342": [1], "1339953828": [1], "1339999622": [1], "1340243249": [1], "1340403927": [1], "1340458480": [1], "1340641658": [1], "1340826070": [1], "1341105275": [1], "1341221384": [1], "1341612616": [1], "1341774752": [1], "1341944224": [1], "1342418723": [1], "1342561631": [1], "1343005677": [1], "1343398352": [1], "1343494260": [1], "1343531069": [1], "1343684325": [1], "1343761612": [1], "1343877389": [1], "1344003398": [1], "1344303202": [1], "1344352857": [1], "1344459448": [1], "1344541128": [1], "1344777859": [1], "1344870691": [1], "1345373118": [1], "1345568585": [1], "1345672716": [1], "1346054889": [1], "1346390737": [1], "1346404759": [1], "1346521968": [1], "1346662559": [1], "1347708231": [1], "1347908360": [1], "1347935617": [1], "1348007203": [1], "1348054797": [1], "1348098500": [1], "1348296204": [1], "1348392001": [1], "1348564405": [1], "1348674171": [1], "1349529703": [1], "1349686826": [1], "1350591224": [1], "1351160235": [1], "1351179828": [1], "1351257483": [1], "1351675131": [1], "1351988563": [1], "1352202118": [1], "1352240309": [1], "1352256026": [1], "1352265526": [1], "1352546001": [1], "1352555260": [1], "1352766880": [1], "1352794437": [1], "1352844553": [1], "1352869123": [1], "1353071937": [1], "1353161474": [1], "1353175615": [1], "1353189791": [1], "1353346786": [1], "1353440753": [1], "1353470153": [1], "1353929766": [1], "1354103491": [1], "1354438664": [1], "1354737901": [1], "1354774536": [1], "1354817356": [1], "1354884406": [1], "1354930950": [1], "1355013093": [1], "1355040462": [1], "1355170581": [1], "1355509970": [1], "1355593108": [1], "1355967751": [1], "1356086874": [1], "1356346854": [1], "1357225652": [1], "1358176253": [1], "1358558861": [1], "1358891899": [1], "1358957540": [1], "1360361572": [1], "1360459443": [1], "1360858890": [1], "1361080832": [1], "1361186278": [1], "1361187581": [1], "1361330862": [1], "1361445292": [1], "1361513582": [1], "1361718493": [1], "1362015040": [1], "1362155931": [1], "1362413365": [1], "1362448931": [1], "1362563744": [1], "1362844596": [1], "1363011200": [1], "1363273204": [1], "1363704516": [1], "1363767286": [1], "1363821202": [1], "1364610551": [1], "1364749312": [1], "1364913191": [1], "1365090511": [1], "1365176703": [1], "1365509765": [1], "1366521432": [1], "1366627284": [1], "1366826469": [1], "1366889907": [1], "1367093196": [1], "1367387460": [1], "1367458644": [1], "1367687680": [1], "1367707795": [1], "1367903141": [1], "1368319841": [1], "1368443920": [1], "1368479513": [1], "1368750444": [1], "1368811011": [1], "1368832888": [1], "1368862838": [1], "1368936931": [1], "1369005321": [1], "1369256523": [1], "1369258007": [1], "1369500131": [1], "1369972072": [1], "1370154113": [1], "1370281264": [1], "1370480888": [1], "1370560346": [1], "1370779824": [1], "1371347448": [1], "1371898831": [1], "1372403802": [1], "1372822274": [1], "1372831157": [1], "1372862150": [1], "1373045189": [1], "1373159617": [1], "1373315176": [1], "1373407953": [1], "1373509485": [1], "1373887438": [1], "1374203139": [1], "1374363696": [1], "1375376548": [1], "1375540857": [1], "1375639267": [1], "1375799833": [1], "1375851437": [1], "1376073999": [1], "1376095087": [1], "1376436980": [1], "1377002214": [1], "1377226402": [1], "1377372974": [1], "1377625349": [1], "1377987019": [1], "1378031950": [1], "1378503939": [1], "1378594409": [1], "1378728326": [1], "1378848721": [1], "1378857256": [1], "1378865674": [1], "1379097653": [1], "1379179859": [1], "1379753881": [1], "1379868543": [1], "1380196028": [1], "1380206320": [1], "1380311518": [1], "1380409888": [1], "1380557646": [1], "1380674624": [1], "1380776880": [1], "1380790524": [1], "1380881866": [1], "1380887411": [1], "1380941081": [1], "1381193097": [1], "1381370000": [1], "1381646672": [1], "1381723255": [1], "1382162659": [1], "1382523505": [1], "1383140537": [1], "1383363421": [1], "1383538361": [1], "1383563046": [1], "1384067275": [1], "1384264450": [1], "1384865583": [1], "1385002580": [1], "1385148306": [1], "1385489801": [1], "1385514761": [1], "1386061901": [1], "1386091981": [1], "1386356749": [1], "1386366741": [1], "1386580740": [1], "1386772572": [1], "1387793656": [1], "1387967847": [1], "1387998139": [1], "1388634679": [1], "1388726926": [1], "1388966286": [1], "1389461494": [1], "1389697844": [1], "1390609201": [1], "1391077646": [1], "1391233759": [1], "1391543276": [1], "1391750031": [1], "1391894544": [1], "1392101103": [1], "1392199392": [1], "1392249924": [1], "1392317999": [1], "1392781239": [1], "1392999159": [1], "1393132628": [1], "1393765233": [1], "1393819552": [1], "1394557191": [1], "1394560839": [1], "1395147252": [1], "1395197445": [1], "1395401085": [1], "1395543349": [1], "1395570929": [1], "1395645842": [1], "1395762993": [1], "1395883176": [1], "1395987754": [1], "1396231710": [1], "1396240216": [1], "1396375962": [1], "1396464744": [1], "1396997760": [1], "1397032275": [1], "1397111997": [1], "1397241311": [1], "1397359913": [1], "1397430420": [1], "1397613259": [1], "1398098861": [1], "1398207070": [1], "1398238619": [1], "1398262376": [1], "1398308508": [1], "1398912883": [1], "1398973475": [1], "1398982141": [1], "1399029940": [1], "1399057653": [1], "1399057671": [1], "1399146791": [1], "1399428502": [1], "1399499960": [1], "1399743541": [1], "1400383523": [1], "1400551973": [1], "1400656177": [1], "1400711770": [1], "1400722816": [1], "1401128133": [1], "1401174422": [1], "1402015611": [1], "1402067942": [1], "1402412825": [1], "1402432688": [1], "1402491103": [1], "1402506597": [1], "1402541471": [1], "1402837346": [1], "1402982711": [1], "1403116368": [1], "1403170174": [1], "1403237524": [1], "1403572288": [1], "1403663366": [1], "1404148597": [1], "1405091221": [1], "1405101695": [1], "1406083215": [1], "1406201601": [1], "1406656796": [1], "1406964056": [1], "1407654053": [1], "1407685778": [1], "1407743714": [1], "1407827616": [1], "1408233514": [1], "1408287430": [1], "1408636621": [1], "1408641385": [1], "1408908545": [1], "1408940236": [1], "1409257175": [1], "1409428875": [1], "1409449183": [1], "1410054382": [1], "1410101551": [1], "1410576289": [1], "1410781344": [1], "1411222604": [1], "1411352436": [1], "1411601918": [1], "1411827997": [1], "1411948651": [1], "1411971348": [1], "1411975864": [1], "1411989557": [1], "1412327764": [1], "1412469207": [1], "1413255479": [1], "1413521023": [1], "1413658446": [1], "1413664965": [1], "1414769306": [1], "1415093777": [1], "1415266487": [1], "1415976044": [1], "1416309501": [1], "1416438387": [1], "1416630935": [1], "1417110645": [1], "1417677244": [1], "1418070855": [1], "1418199341": [1], "1418288207": [1], "1418773612": [1], "1418837712": [1], "1419549623": [1], "1419738436": [1], "1420073309": [1], "1420155686": [1], "1420353151": [1], "1420547864": [1], "1420639184": [1], "1421170769": [1], "1421768607": [1], "1422207314": [1], "1422812079": [1], "1423105517": [1], "1423971761": [1], "1424102750": [1], "1424169811": [1], "1424782279": [1], "1425011632": [1], "1425089242": [1], "1425771810": [1], "1425955732": [1], "1426089337": [1], "1426148715": [1], "1426300334": [1], "1426614555": [1], "1426980536": [1], "1427031934": [1], "1427121002": [1], "1427186177": [1], "1428306470": [1], "1428337586": [1], "1428468162": [1], "1428560688": [1], "1428758912": [1], "1428798527": [1], "1429776562": [1], "1430541307": [1], "1430585267": [1], "1430668416": [1], "1430874369": [1], "1430939624": [1], "1431244561": [1], "1431428746": [1], "1431495765": [1], "1431694559": [1], "1431799639": [1], "1432028930": [1], "1432121281": [1], "1432251789": [1], "1432297370": [1], "1432478090": [1], "1432641140": [1], "1432785306": [1], "1433271209": [1], "1433332147": [1], "1433429459": [1], "1433749477": [1], "1433750046": [1], "1434146829": [1], "1434175399": [1], "1435296749": [1], "1436065841": [1], "1436306619": [1], "1436648672": [1], "1437090524": [1], "1437232780": [1], "1437885484": [1], "1437889940": [1], "1438196514": [1], "1438488221": [1], "1438847036": [1], "1438863914": [1], "1439123190": [1], "1439328252": [1], "1439540445": [1], "1439719967": [1], "1439902415": [1], "1439973396": [1], "1439978342": [1], "1440237428": [1], "1440815963": [1], "1441085585": [1], "1441141242": [1], "1441744222": [1], "1441797725": [1], "1441799755": [1], "1442713364": [1], "1442754138": [1], "1442991058": [1], "1443017916": [1], "1443045195": [1], "1443113409": [1], "1443211328": [1], "1443300153": [1], "1443358923": [1], "1444057233": [1], "1444310958": [1], "1444516404": [1], "1444860667": [1], "1445069843": [1], "1445135559": [1], "1445215368": [1], "1445302456": [1], "1445709625": [1], "1445793236": [1], "1446391871": [1], "1446555329": [1], "1446602410": [1], "1446703673": [1], "1447229497": [1], "1447950771": [1], "1448359412": [1], "1448932984": [1], "1449168045": [1], "1449305339": [1], "1449446200": [1], "1449632269": [1], "1449716355": [1], "1449749130": [1], "1449782656": [1], "1449933174": [1], "1450178777": [1], "1450447737": [1], "1450510618": [1], "1451201888": [1], "1451253443": [1], "1451832189": [1], "1451953100": [1], "1452102676": [1], "1452395386": [1], "1452428299": [1], "1452710458": [1], "1454739717": [1], "1455102106": [1], "1455417175": [1], "1455587555": [1], "1456052183": [1], "1456645739": [1], "1456858440": [1], "1457223342": [1], "1457419905": [1], "1458053946": [1], "1458362984": [1], "1459455639": [1], "1460034363": [1], "1460167757": [1], "1460383794": [1], "1460559857": [1], "1460696746": [1], "1461174153": [1], "1461210143": [1], "1461265108": [1], "1461663750": [1], "1461756184": [1], "1461775631": [1], "1461911807": [1], "1462149813": [1], "1462337840": [1], "1462650226": [1], "1462803006": [1], "1462843170": [1], "1463151427": [1], "1463250617": [1], "1463327585": [1], "1463806439": [1], "1463814328": [1], "1463931957": [1], "1464277155": [1], "1464293171": [1], "1464302705": [1], "1464376020": [1], "1464563190": [1], "1464914027": [1], "1464987738": [1], "1465004643": [1], "1465035319": [1], "1465114911": [1], "1465256909": [1], "1465619971": [1], "1466186300": [1], "1466275996": [1], "1466620592": [1], "1467928094": [1], "1468165357": [1], "1468449593": [1], "1468521117": [1], "1468609280": [1], "1468691886": [1], "1469178544": [1], "1469396960": [1], "1469847051": [1], "1469930617": [1], "1470218457": [1], "1470449882": [1], "1471008591": [1], "1471152174": [1], "1471205682": [1], "1472046650": [1], "1472162272": [1], "1472554026": [1], "1473065940": [1], "1473503587": [1], "1473674449": [1], "1473977787": [1], "1474258771": [1], "1474358955": [1], "1474510474": [1], "1474511482": [1], "1474606509": [1], "1475207700": [1], "1475339096": [1], "1475369080": [1], "1475412229": [1], "1475648173": [1], "1475724461": [1], "1475757274": [1], "1475800813": [1], "1475910301": [1], "1476194609": [1], "1476500985": [1], "1476546089": [1], "1476580805": [1], "1476802463": [1], "1476843389": [1], "1476990840": [1], "1477631559": [1], "1478145427": [1], "1478280381": [1], "1478480722": [1], "1478747602": [1], "1478747982": [1], "1478832579": [1], "1478846165": [1], "1478970554": [1], "1479016266": [1], "1479058403": [1], "1479239327": [1], "1479659943": [1], "1479708200": [1], "1479836406": [1], "1479958185": [1], "1480079478": [1], "1480260965": [1], "1480358699": [1], "1480368457": [1], "1480481460": [1], "1481235518": [1], "1481302694": [1], "1481442513": [1], "1481529140": [1], "1481707099": [1], "1481979872": [1], "1482642951": [1], "1482651725": [1], "1482971031": [1], "1484577073": [1], "1484607196": [1], "1484773169": [1], "1485194036": [1], "1485416461": [1], "1485611269": [1], "1485756393": [1], "1486076319": [1], "1486405298": [1], "1486503446": [1], "1486843736": [1], "1486994990": [1], "1487049998": [1], "1487088463": [1], "1487508297": [1], "1487605348": [1], "1488092673": [1], "1488362429": [1], "1488553881": [1], "1488677034": [1], "1488739302": [1], "1488902957": [1], "1489093789": [1], "1489301958": [1], "1489542802": [1], "1489568553": [1], "1489665498": [1], "1489744669": [1], "1489841283": [1], "1489948055": [1], "1489981267": [1], "1490404424": [1], "1490467055": [1], "1490719961": [1], "1490918128": [1], "1490974986": [1], "1491259508": [1], "1491415665": [1], "1491421624": [1], "1491590912": [1], "1491685508": [1], "1491723181": [1], "1492748698": [1], "1492833980": [1], "1493311026": [1], "1494188311": [1], "1494246308": [1], "1494365268": [1], "1494418988": [1], "1494841167": [1], "1495070006": [1], "1495242832": [1], "1495404612": [1], "1495583219": [1], "1495974135": [1], "1496234127": [1], "1496488544": [1], "1496766082": [1], "1496859581": [1], "1496872923": [1], "1497260956": [1], "1497459231": [1], "1497579600": [1], "1497666565": [1], "1497694830": [1], "1498030752": [1], "1498067859": [1], "1498241416": [1], "1498451780": [1], "1498972199": [1], "1499003531": [1], "1499131518": [1], "1499308564": [1], "1499584178": [1], "1499599446": [1], "1499759120": [1], "1499899663": [1], "1500102652": [1], "1500360186": [1], "1500554586": [1], "1500838057": [1], "1501106486": [1], "1501267745": [1], "1501293157": [1], "1502112379": [1], "1502292566": [1], "1502389107": [1], "1502523013": [1], "1502826287": [1], "1503416986": [1], "1503525028": [1], "1504118117": [1], "1504140193": [1], "1504294522": [1], "1504437672": [1], "1505281450": [1], "1505336829": [1], "1505548901": [1], "1505666396": [1], "1505797947": [1], "1505832499": [1], "1506341022": [1], "1506741126": [1], "1506908081": [1], "1507151347": [1], "1507179552": [1], "1507716851": [1], "1508155598": [1], "1508212870": [1], "1508477103": [1], "1508896972": [1], "1509377484": [1], "1509528821": [1], "1510331182": [1], "1510907901": [1], "1511026544": [1], "1511595574": [1], "1512142276": [1], "1512390118": [1], "1512410608": [1], "1512414131": [1], "1512675703": [1], "1513497773": [1], "1513552073": [1], "1513592066": [1], "1513837778": [1], "1513900520": [1], "1514072399": [1], "1514622755": [1], "1514732292": [1], "1515076144": [1], "1515134255": [1], "1515486157": [1], "1515745050": [1], "1515787172": [1], "1515854845": [1], "1516042808": [1], "1516074606": [1], "1516218925": [1], "1516496294": [1], "1516708919": [1], "1517103939": [1], "1517263212": [1], "1517333887": [1], "1517430596": [1], "1517937501": [1], "1517975280": [1], "1518358174": [1], "1518460164": [1], "1518725876": [1], "1519020457": [1], "1519225687": [1], "1519248141": [1], "1519560178": [1], "1519618427": [1], "1520014218": [1], "1520060786": [1], "1520089805": [1], "1520093760": [1], "1520531335": [1], "1520816175": [1], "1520861591": [1], "1520873662": [1], "1520936441": [1], "1521227015": [1], "1521285533": [1], "1521500704": [1], "1521671566": [1], "1521707042": [1], "1521973417": [1], "1522002118": [1], "1522023484": [1], "1522055027": [1], "1522134992": [1], "1522535438": [1], "1522580556": [1], "1522591237": [1], "1522678811": [1], "1522707059": [1], "1522719143": [1], "1522778327": [1], "1522974874": [1], "1524022789": [1], "1524047401": [1], "1524074048": [1], "1524464264": [1], "1524733282": [1], "1524791640": [1], "1524863014": [1], "1525106639": [1], "1525342480": [1], "1525579131": [1], "1525868378": [1], "1525878419": [1], "1526025221": [1], "1526647354": [1], "1526717523": [1], "1526966315": [1], "1527037303": [1], "1527594184": [1], "1527707359": [1], "1528605667": [1], "1528624228": [1], "1528748396": [1], "1528859788": [1], "1529303020": [1], "1529447872": [1], "1529561349": [1], "1529649642": [1], "1529773729": [1], "1529874646": [1], "1529901915": [1], "1530538235": [1], "1530566179": [1], "1530676746": [1], "1531002686": [1], "1531119222": [1], "1531141733": [1], "1531350795": [1], "1531500789": [1], "1531732202": [1], "1531788368": [1], "1531818795": [1], "1532185980": [1], "1532311371": [1], "1532655693": [1], "1532682012": [1], "1532838404": [1], "1532840942": [1], "1532892156": [1], "1533272628": [1], "1534265309": [1], "1534457469": [1], "1535102131": [1], "1535136618": [1], "1535146509": [1], "1535257440": [1], "1535273835": [1], "1535692379": [1], "1536103712": [1], "1536320057": [1], "1536379831": [1], "1536718016": [1], "1536755901": [1], "1537416158": [1], "1537482971": [1], "1537531481": [1], "1537603838": [1], "1538090709": [1], "1538366368": [1], "1538667538": [1], "1538810696": [1], "1538857196": [1], "1539154497": [1], "1539476625": [1], "1539598600": [1], "1539685184": [1], "1540164196": [1], "1540193373": [1], "1540299354": [1], "1540675695": [1], "1540791588": [1], "1540961696": [1], "1541252135": [1], "1541271618": [1], "1541362222": [1], "1541651870": [1], "1541685629": [1], "1541854095": [1], "1542044258": [1], "1542140041": [1], "1542143367": [1], "1542363517": [1], "1543049847": [1], "1543291986": [1], "1543310196": [1], "1543452454": [1], "1543463946": [1], "1543473995": [1], "1543485219": [1], "1543494651": [1], "1544126101": [1], "1544221537": [1], "1545289312": [1], "1546084850": [1], "1546267923": [1], "1546473829": [1], "1546669512": [1], "1546935800": [1], "1547063495": [1], "1547175183": [1], "1547333218": [1], "1547558675": [1], "1547559067": [1], "1548415756": [1], "1549211089": [1], "1549380587": [1], "1549451766": [1], "1549991958": [1], "1550252395": [1], "1550315636": [1], "1550739768": [1], "1550876879": [1], "1550947722": [1], "1551728963": [1], "1551986034": [1], "1552253548": [1], "1552653559": [1], "1552663895": [1], "1552665612": [1], "1552737314": [1], "1552739360": [1], "1553177510": [1], "1553622803": [1], "1553660723": [1], "1553682003": [1], "1554665678": [1], "1554876581": [1], "1555054163": [1], "1555074598": [1], "1555117740": [1], "1555128616": [1], "1555640171": [1], "1556161080": [1], "1556356109": [1], "1556601297": [1], "1556757934": [1], "1556789497": [1], "1556958836": [1], "1557168466": [1], "1558169295": [1], "1558527341": [1], "1558717974": [1], "1558868358": [1], "1559044645": [1], "1559093940": [1], "1559185336": [1], "1559212550": [1], "1559478753": [1], "1559704402": [1], "1559916701": [1], "1560861505": [1], "1561020658": [1], "1561132257": [1], "1561496045": [1], "1561630300": [1], "1561731213": [1], "1561960889": [1], "1562256647": [1], "1562648649": [1], "1562826632": [1], "1563129429": [1], "1563447598": [1], "1563811499": [1], "1564227789": [1], "1564689091": [1], "1564891319": [1], "1564909743": [1], "1564935922": [1], "1564990811": [1], "1565161757": [1], "1565590430": [1], "1566190428": [1], "1566258422": [1], "1566338915": [1], "1566516123": [1], "1566660953": [1], "1566753667": [1], "1566980257": [1], "1567044271": [1], "1567092018": [1], "1567199667": [1], "1567243416": [1], "1567306235": [1], "1567618158": [1], "1567930564": [1], "1568098952": [1], "1568146003": [1], "1568255303": [1], "1568291053": [1], "1568605996": [1], "1568628305": [1], "1568649189": [1], "1568713774": [1], "1569077860": [1], "1569147815": [1], "1569330359": [1], "1569540486": [1], "1570048551": [1], "1570533215": [1], "1570689988": [1], "1570747712": [1], "1570754971": [1], "1570793795": [1], "1570935616": [1], "1570959222": [1], "1571042094": [1], "1571077537": [1], "1571133309": [1], "1571335201": [1], "1571351698": [1], "1571362379": [1], "1571463499": [1], "1571791570": [1], "1572114553": [1], "1572202530": [1], "1572385240": [1], "1572516827": [1], "1572594245": [1], "1573037149": [1], "1573126037": [1], "1573195002": [1], "1573445918": [1], "1573490546": [1], "1573504628": [1], "1573754841": [1], "1574220430": [1], "1574430362": [1], "1574476528": [1], "1575150097": [1], "1575235804": [1], "1575278969": [1], "1575395439": [1], "1575406169": [1], "1575446774": [1], "1575712685": [1], "1575743862": [1], "1576437003": [1], "1576669103": [1], "1577017314": [1], "1577367978": [1], "1577434717": [1], "1577452581": [1], "1577514001": [1], "1577622025": [1], "1577812089": [1], "1578048406": [1], "1578270755": [1], "1578770047": [1], "1578853980": [1], "1578934421": [1], "1578944223": [1], "1579027543": [1], "1579048090": [1], "1579099449": [1], "1579838742": [1], "1580434700": [1], "1580690653": [1], "1580741856": [1], "1580760101": [1], "1580953245": [1], "1581072448": [1], "1581198959": [1], "1581579619": [1], "1581833370": [1], "1582420439": [1], "1582915590": [1], "1583368195": [1], "1583494487": [1], "1583586512": [1], "1584073234": [1], "1584121985": [1], "1585090862": [1], "1585239286": [1], "1585421636": [1], "1585690456": [1], "1585706747": [1], "1585738010": [1], "1585739115": [1], "1585958935": [1], "1586142369": [1], "1586246218": [1], "1587019972": [1], "1587180449": [1], "1587348751": [1], "1587959235": [1], "1588277530": [1], "1588782172": [1], "1588856806": [1], "1588879401": [1], "1589147301": [1], "1589177307": [1], "1589288789": [1], "1589402121": [1], "1589635762": [1], "1589705219": [1], "1590009990": [1], "1590217621": [1], "1590478140": [1], "1590784448": [1], "1591312829": [1], "1591388819": [1], "1591741838": [1], "1591945806": [1], "1592490824": [1], "1592763527": [1], "1593003305": [1], "1593132128": [1], "1593145684": [1], "1593181207": [1], "1593870207": [1], "1594390295": [1], "1594638134": [1], "1594829953": [1], "1595621312": [1], "1595722498": [1], "1596164925": [1], "1596414385": [1], "1596473143": [1], "1596486333": [1], "1596746395": [1], "1596766569": [1], "1596850011": [1], "1597097868": [1], "1597282283": [1], "1597458283": [1], "1597509030": [1], "1597821821": [1], "1598286282": [1], "1598537901": [1], "1598539754": [1], "1598688863": [1], "1598734245": [1], "1598986190": [1], "1599479886": [1], "1599637304": [1], "1599767507": [1], "1600091063": [1], "1600495569": [1], "1600847937": [1], "1600955507": [1], "1600960579": [1], "1601046153": [1], "1601110578": [1], "1601453159": [1], "1601470594": [1], "1602337394": [1], "1602459429": [1], "1602903343": [1], "1603079622": [1], "1603198191": [1], "1603280167": [1], "1603375383": [1], "1603479207": [1], "1603721816": [1], "1603873013": [1], "1604035109": [1], "1604145567": [1], "1604437359": [1], "1604578291": [1], "1604988400": [1], "1605295789": [1], "1605728161": [1], "1605777117": [1], "1605816700": [1], "1605830911": [1], "1606087533": [1], "1606350162": [1], "1606460924": [1], "1606508930": [1], "1607487361": [1], "1607501729": [1], "1607635911": [1], "1607701478": [1], "1607791596": [1], "1608364335": [1], "1608473836": [1], "1608891571": [1], "1608898463": [1], "1609343268": [1], "1609376818": [1], "1609572309": [1], "1610023724": [1], "1610137295": [1], "1610390264": [1], "1610518046": [1], "1611279206": [1], "1612425332": [1], "1612445595": [1], "1612468626": [1], "1612986740": [1], "1613024945": [1], "1613206270": [1], "1613341671": [1], "1613459412": [1], "1613520255": [1], "1614137010": [1], "1614274652": [1], "1615094743": [1], "1615275457": [1], "1615400917": [1], "1615479494": [1], "1615798861": [1], "1616435580": [1], "1616502526": [1], "1616605516": [1], "1616905698": [1], "1617433210": [1], "1617563355": [1], "1617795538": [1], "1617868880": [1], "1617955629": [1], "1618225673": [1], "1618371338": [1], "1618435813": [1], "1618470212": [1], "1618519400": [1], "1618839509": [1], "1619038482": [1], "1619289973": [1], "1619298835": [1], "1619469188": [1], "1619554198": [1], "1619635529": [1], "1619835015": [1], "1619891004": [1], "1619951466": [1], "1620244833": [1], "1620769782": [1], "1620791683": [1], "1621156120": [1], "1621189768": [1], "1621297648": [1], "1621682963": [1], "1621881570": [1], "1622211139": [1], "1622268606": [1], "1622468241": [1], "1623176802": [1], "1623722613": [1], "1623869326": [1], "1624101242": [1], "1624347800": [1], "1624408760": [1], "1624839668": [1], "1625103081": [1], "1625366915": [1], "1625437998": [1], "1625459070": [1], "1625538044": [1], "1625585866": [1], "1625832243": [1], "1626080326": [1], "1626326515": [1], "1626401341": [1], "1626636809": [1], "1626682653": [1], "1626973008": [1], "1627252840": [1], "1627578291": [1], "1627785325": [1], "1628172422": [1], "1628486746": [1], "1629431134": [1], "1629583594": [1], "1629929303": [1], "1630002454": [1], "1630075013": [1], "1630272838": [1], "1630640596": [1], "1630647318": [1], "1630886824": [1], "1631019388": [1], "1631480868": [1], "1631723546": [1], "1631890180": [1], "1632104383": [1], "1632283591": [1], "1632950492": [1], "1633018820": [1], "1633283179": [1], "1633336941": [1], "1633382022": [1], "1633445392": [1], "1633508749": [1], "1633791817": [1], "1633926816": [1], "1633965935": [1], "1634228136": [1], "1634436003": [1], "1635238543": [1], "1635654478": [1], "1635933732": [1], "1635960762": [1], "1636018418": [1], "1636019764": [1], "1636112471": [1], "1636964096": [1], "1637059904": [1], "1637384237": [1], "1638825189": [1], "1639079545": [1], "1639211494": [1], "1639242586": [1], "1639308381": [1], "1639698074": [1], "1639733465": [1], "1640163401": [1], "1640964874": [1], "1641325477": [1], "1641561213": [1], "1641666549": [1], "1641898347": [1], "1642222554": [1], "1642368873": [1], "1642621781": [1], "1642650026": [1], "1642671533": [1], "1642811788": [1], "1643389568": [1], "1643399034": [1], "1643627698": [1], "1643802683": [1], "1643905596": [1], "1644371392": [1], "1644394873": [1], "1644396729": [1], "1644848007": [1], "1645324278": [1], "1645515905": [1], "1645636572": [1], "1645653795": [1], "1645795092": [1], "1646154114": [1], "1646868595": [1], "1646950643": [1], "1647341651": [1], "1647667779": [1], "1647674996": [1], "1647681334": [1], "1648201590": [1], "1648410970": [1], "1648590564": [1], "1648898593": [1], "1649324601": [1], "1649448187": [1], "1649659509": [1], "1649661421": [1], "1649775592": [1], "1650042231": [1], "1650255749": [1], "1650317810": [1], "1650988081": [1], "1651068165": [1], "1651354298": [1], "1651662775": [1], "1651679349": [1], "1652186966": [1], "1652450920": [1], "1652799798": [1], "1652997920": [1], "1653730908": [1], "1654129021": [1], "1654262321": [1], "1654283163": [1], "1654556837": [1], "1654637943": [1], "1655341269": [1], "1655370770": [1], "1655386158": [1], "1655514329": [1], "1656232256": [1], "1656257393": [1], "1657041010": [1], "1657357741": [1], "1657563756": [1], "1657645713": [1], "1658359359": [1], "1658401049": [1], "1658895749": [1], "1659391765": [1], "1659522333": [1], "1659917804": [1], "1660207623": [1], "1660644258": [1], "1660696096": [1], "1660899652": [1], "1661712043": [1], "1662042392": [1], "1662357488": [1], "1662373166": [1], "1662715158": [1], "1662940931": [1], "1663813178": [1], "1663831361": [1], "1664016028": [1], "1664426468": [1], "1664805756": [1], "1664888979": [1], "1665081184": [1], "1665086631": [1], "1665131861": [1], "1665225350": [1], "1665474871": [1], "1665523624": [1], "1665673273": [1], "1665703901": [1], "1665923334": [1], "1665980316": [1], "1666164926": [1], "1666454011": [1], "1666710634": [1], "1666752701": [1], "1666800156": [1], "1666954904": [1], "1667081019": [1], "1667139608": [1], "1667176814": [1], "1667314642": [1], "1667716444": [1], "1667885289": [1], "1667951268": [1], "1668627533": [1], "1669092777": [1], "1669607579": [1], "1669714139": [1], "1669804960": [1], "1670133545": [1], "1670545023": [1], "1670579959": [1], "1670666810": [1], "1670796259": [1], "1671503658": [1], "1671952454": [1], "1672421613": [1], "1672900610": [1], "1673276827": [1], "1673870148": [1], "1673938644": [1], "1674690745": [1], "1674980379": [1], "1675048306": [1], "1675065191": [1], "1675310348": [1], "1675398837": [1], "1675514630": [1], "1675749549": [1], "1675990121": [1], "1676112416": [1], "1676338213": [1], "1676512463": [1], "1676594773": [1], "1676924661": [1], "1676998850": [1], "1677114091": [1], "1677929229": [1], "1679163136": [1], "1679223796": [1], "1679403718": [1], "1679427449": [1], "1679772797": [1], "1680191276": [1], "1680298417": [1], "1680524143": [1], "1680552522": [1], "1680737327": [1], "1680967825": [1], "1681046112": [1], "1681339549": [1], "1681868178": [1], "1682076482": [1], "1682304802": [1], "1682498651": [1], "1682698497": [1], "1682808687": [1], "1683000252": [1], "1683450537": [1], "1683495948": [1], "1683548438": [1], "1683564519": [1], "1683944947": [1], "1684046644": [1], "1684181189": [1], "1684524808": [1], "1684749678": [1], "1684918491": [1], "1685067331": [1], "1685216155": [1], "1685558211": [1], "1685661895": [1], "1686554163": [1], "1686593024": [1], "1686671286": [1], "1687180049": [1], "1687336974": [1], "1687528170": [1], "1687866536": [1], "1688039250": [1], "1688138358": [1], "1688206016": [1], "1688610947": [1], "1688703594": [1], "1689294448": [1], "1689499867": [1], "1689799253": [1], "1690196414": [1], "1690419229": [1], "1690423558": [1], "1690536429": [1], "1690589299": [1], "1690745754": [1], "1691105202": [1], "1691552719": [1], "1691617929": [1], "1692602100": [1], "1692743544": [1], "1692784496": [1], "1693060305": [1], "1693110284": [1], "1693130372": [1], "1693614130": [1], "1693956247": [1], "1694193842": [1], "1694885616": [1], "1695035310": [1], "1695115601": [1], "1695322548": [1], "1695509086": [1], "1695898616": [1], "1696312985": [1], "1696584181": [1], "1697526543": [1], "1697582539": [1], "1698818238": [1], "1698956089": [1], "1699123808": [1], "1699142826": [1], "1699253786": [1], "1699407079": [1], "1699540696": [1], "1699634265": [1], "1699798758": [1], "1699863110": [1], "1700044427": [1], "1700494274": [1], "1700708675": [1], "1700759296": [1], "1700958567": [1], "1701055578": [1], "1701074222": [1], "1701168204": [1], "1701600974": [1], "1701813046": [1], "1701889610": [1], "1701890846": [1], "1701920188": [1], "1702285088": [1], "1702326672": [1], "1702704250": [1], "1703118044": [1], "1703277260": [1], "1703586559": [1], "1703745231": [1], "1703795042": [1], "1703866901": [1], "1703928251": [1], "1704080352": [1], "1704294000": [1], "1704371544": [1], "1704418998": [1], "1705244738": [1], "1705585209": [1], "1706292847": [1], "1707147152": [1], "1707439202": [1], "1707446400": [1], "1707565637": [1], "1707589719": [1], "1707881974": [1], "1707929877": [1], "1708132269": [1], "1708252017": [1], "1709026874": [1], "1709939708": [1], "1709962890": [1], "1709973352": [1], "1710469473": [1], "1710897237": [1], "1711042511": [1], "1711184111": [1], "1711429319": [1], "1711449525": [1], "1711654208": [1], "1711694183": [1], "1711777715": [1], "1711881765": [1], "1712564853": [1], "1712597938": [1], "1713039515": [1], "1713171248": [1], "1713413018": [1], "1713718315": [1], "1713795125": [1], "1714315241": [1], "1714406549": [1], "1714956833": [1], "1715191179": [1], "1715208473": [1], "1715292896": [1], "1716327510": [1], "1716357028": [1], "1716621706": [1], "1717510158": [1], "1717525490": [1], "1717660429": [1], "1718063031": [1], "1718349968": [1], "1718396143": [1], "1718417669": [1], "1718434478": [1], "1718692025": [1], "1718773907": [1], "1719328017": [1], "1719703626": [1], "1719720258": [1], "1719783953": [1], "1719806158": [1], "1719901387": [1], "1720325044": [1], "1720479598": [1], "1720493809": [1], "1720861619": [1], "1720881495": [1], "1721437825": [1], "1722385892": [1], "1722677007": [1], "1722938021": [1], "1722969834": [1], "1723148725": [1], "1723219445": [1], "1724011407": [1], "1724188675": [1], "1724557524": [1], "1725080824": [1], "1725324762": [1], "1725497123": [1], "1725687283": [1], "1725798296": [1], "1726894946": [1], "1727513181": [1], "1727552519": [1], "1727784820": [1], "1727870086": [1], "1727894982": [1], "1727914702": [1], "1727961838": [1], "1728152243": [1], "1728171512": [1], "1728483411": [1], "1728617473": [1], "1728697101": [1], "1728721594": [1], "1728929641": [1], "1729039265": [1], "1729585997": [1], "1729598341": [1], "1729693187": [1], "1729911117": [1], "1729923423": [1], "1730166599": [1], "1730677568": [1], "1730795995": [1], "1730998547": [1], "1731105544": [1], "1731231547": [1], "1731274472": [1], "1731717247": [1], "1731806752": [1], "1731977134": [1], "1732183316": [1], "1732320127": [1], "1733719052": [1], "1734127536": [1], "1734312674": [1], "1734539256": [1], "1734556151": [1], "1734655489": [1], "1735195728": [1], "1735225608": [1], "1735480902": [1], "1735614305": [1], "1735827453": [1], "1736028617": [1], "1736067242": [1], "1736230718": [1], "1736720004": [1], "1736767875": [1], "1736989411": [1], "1737589429": [1], "1737634672": [1], "1737682037": [1], "1737775965": [1], "1737863499": [1], "1738114092": [1], "1738981138": [1], "1739263812": [1], "1739401508": [1], "1739544503": [1], "1739545954": [1], "1739940812": [1], "1740188142": [1], "1740960238": [1], "1741119525": [1], "1741147246": [1], "1741666403": [1], "1742929486": [1], "1743748161": [1], "1744599485": [1], "1744615367": [1], "1745011148": [1], "1745077506": [1], "1745572887": [1], "1746082614": [1], "1746130176": [1], "1746201256": [1], "1746205469": [1], "1746409138": [1], "1746428129": [1], "1746579183": [1], "1746596795": [1], "1746683106": [1], "1746774175": [1], "1746834454": [1], "1746944210": [1], "1747085206": [1], "1747100490": [1], "1747295366": [1], "1747389423": [1], "1747492131": [1], "1747719450": [1], "1747826419": [1], "1748187758": [1], "1748199673": [1], "1749144260": [1], "1749224479": [1], "1749377700": [1], "1749915072": [1], "1750222489": [1], "1750244741": [1], "1750499351": [1], "1750557669": [1], "1750938638": [1], "1751000319": [1], "1751310138": [1], "1751574631": [1], "1751885268": [1], "1752115328": [1], "1752134302": [1], "1752284736": [1], "1752294393": [1], "1752300589": [1], "1752578917": [1], "1752626584": [1], "1752797615": [1], "1752993732": [1], "1753342906": [1], "1754002051": [1], "1754088693": [1], "1754286353": [1], "1754882324": [1], "1755041704": [1], "1755591010": [1], "1756116037": [1], "1756467769": [1], "1756800620": [1], "1756833364": [1], "1756963565": [1], "1757533406": [1], "1757710072": [1], "1757724856": [1], "1758072870": [1], "1758136404": [1], "1758680067": [1], "1758974255": [1], "1759091917": [1], "1759614358": [1], "1759786710": [1], "1760464917": [1], "1760842087": [1], "1761340843": [1], "1761660211": [1], "1761838144": [1], "1762328069": [1], "1762349453": [1], "1762591793": [1], "1762620041": [1], "1762654786": [1], "1762750495": [1], "1763280923": [1], "1764056434": [1], "1764320052": [1], "1764555001": [1], "1765504075": [1], "1765512835": [1], "1765736029": [1], "1765783341": [1], "1766332727": [1], "1766603168": [1], "1767332029": [1], "1767361588": [1], "1767537129": [1], "1768784102": [1], "1769090946": [1], "1769154645": [1], "1769262383": [1], "1769368055": [1], "1769413705": [1], "1770110155": [1], "1770145795": [1], "1770405384": [1], "1770612223": [1], "1770738395": [1], "1771219129": [1], "1771621479": [1], "1772015265": [1], "1772140989": [1], "1772437770": [1], "1772457286": [1], "1773104602": [1], "1773183680": [1], "1773210490": [1], "1773253769": [1], "1773885577": [1], "1774163954": [1], "1774583695": [1], "1775100640": [1], "1775130405": [1], "1775183378": [1], "1775377064": [1], "1775950747": [1], "1776237673": [1], "1776267586": [1], "1777746923": [1], "1777779645": [1], "1777857059": [1], "1778024899": [1], "1778278491": [1], "1778317926": [1], "1778830383": [1], "1779204258": [1], "1779208293": [1], "1779276322": [1], "1779571384": [1], "1780034332": [1], "1780077444": [1], "1780822792": [1], "1781353265": [1], "1781426102": [1], "1782349059": [1], "1783031438": [1], "1783257007": [1], "1783701561": [1], "1783845424": [1], "1783964613": [1], "1784088237": [1], "1784214796": [1], "1784279212": [1], "1784300608": [1], "1785018295": [1], "1785148118": [1], "1786257247": [1], "1786269597": [1], "1786330296": [1], "1786355557": [1], "1786719193": [1], "1787175603": [1], "1787348140": [1], "1787694076": [1], "1788248188": [1], "1788463904": [1], "1788572663": [1], "1788733938": [1], "1788784719": [1], "1789259821": [1], "1789555134": [1], "1789621467": [1], "1789649007": [1], "1789734406": [1], "1789785348": [1], "1789833164": [1], "1789866515": [1], "1790293558": [1], "1790343376": [1], "1790831033": [1], "1791508853": [1], "1791574719": [1], "1791944003": [1], "1792414740": [1], "1792429828": [1], "1792581290": [1], "1793108564": [1], "1793475331": [1], "1794060171": [1], "1794062720": [1], "1794102697": [1], "1794780464": [1], "1794984677": [1], "1794995939": [1], "1795587621": [1], "1795857498": [1], "1796055930": [1], "1796598897": [1], "1796833485": [1], "1797046115": [1], "1797076632": [1], "1797393004": [1], "1797397707": [1], "1797702679": [1], "1797880886": [1], "1798441940": [1], "1798470799": [1], "1798690096": [1], "1798700363": [1], "1799027638": [1], "1799093025": [1], "1799208978": [1], "1799346476": [1], "1799512650": [1], "1800218467": [1], "1800253610": [1], "1800421430": [1], "1801065331": [1], "1801322351": [1], "1801950655": [1], "1802227058": [1], "1802548800": [1], "1802590857": [1], "1802963290": [1], "1803000562": [1], "1803131321": [1], "1803653582": [1], "1803912483": [1], "1804047805": [1], "1804468011": [1], "1805153307": [1], "1805416657": [1], "1805655275": [1], "1805810785": [1], "1806465825": [1], "1806635988": [1], "1806763717": [1], "1806820969": [1], "1806853207": [1], "1806998731": [1], "1807011719": [1], "1807096800": [1], "1807400452": [1], "1807963711": [1], "1807997808": [1], "1808164755": [1], "1808485058": [1], "1808874449": [1], "1809620847": [1], "1810214365": [1], "1810365301": [1], "1810682551": [1], "1810871800": [1], "1810930596": [1], "1811061080": [1], "1811152049": [1], "1811187340": [1], "1811837525": [1], "1812069955": [1], "1812252503": [1], "1812391317": [1], "1813351003": [1], "1813905101": [1], "1814395315": [1], "1814551029": [1], "1815013163": [1], "1815324122": [1], "1815917497": [1], "1816031518": [1], "1816364290": [1], "1816708213": [1], "1817222953": [1], "1817491204": [1], "1818632307": [1], "1818842869": [1], "1820137851": [1], "1820245750": [1], "1820347455": [1], "1820567527": [1], "1820818260": [1], "1820910256": [1], "1821124660": [1], "1821128242": [1], "1821131065": [1], "1821253147": [1], "1821733591": [1], "1823237228": [1], "1823775210": [1], "1823977603": [1], "1824423379": [1], "1824564054": [1], "1824638793": [1], "1824651983": [1], "1824751588": [1], "1824936625": [1], "1825909008": [1], "1825923736": [1], "1825979399": [1], "1826688479": [1], "1826732473": [1], "1826848693": [1], "1827144316": [1], "1827328361": [1], "1827507895": [1], "1827578296": [1], "1828500723": [1], "1828662219": [1], "1828941602": [1], "1829028270": [1], "1829070205": [1], "1829463124": [1], "1829955470": [1], "1830473571": [1], "1830476969": [1], "1830551449": [1], "1830680887": [1], "1831103429": [1], "1831110037": [1], "1831185282": [1], "1831201168": [1], "1832000180": [1], "1832023876": [1], "1832135709": [1], "1832362894": [1], "1832734594": [1], "1832787538": [1], "1832847644": [1], "1833403645": [1], "1833452674": [1], "1833863468": [1], "1834005432": [1], "1834117257": [1], "1834255990": [1], "1834498755": [1], "1834677864": [1], "1835699831": [1], "1835715295": [1], "1835910261": [1], "1836032517": [1], "1836131723": [1], "1836263670": [1], "1836343293": [1], "1836674677": [1], "1836913025": [1], "1837229322": [1], "1837742668": [1], "1837904828": [1], "1838574438": [1], "1838956721": [1], "1839512080": [1], "1839782023": [1], "1839839646": [1], "1839869631": [1], "1839942448": [1], "1840168632": [1], "1840330081": [1], "1840379505": [1], "1840515745": [1], "1840517015": [1], "1840583506": [1], "1840865451": [1], "1841010794": [1], "1841037143": [1], "1841070648": [1], "1841382538": [1], "1841501594": [1], "1842110809": [1], "1842248263": [1], "1842417395": [1], "1842695219": [1], "1842900965": [1], "1842922917": [1], "1843088857": [1], "1843093657": [1], "1843207488": [1], "1843247714": [1], "1843448504": [1], "1843541775": [1], "1843545813": [1], "1843630627": [1], "1843655028": [1], "1844241264": [1], "1844281251": [1], "1844313732": [1], "1844339179": [1], "1844384968": [1], "1844694350": [1], "1844747193": [1], "1844797374": [1], "1844945850": [1], "1845543647": [1], "1845546602": [1], "1846211883": [1], "1846219596": [1], "1846439807": [1], "1846533801": [1], "1846617080": [1], "1846969946": [1], "1847606925": [1], "1847754808": [1], "1847961548": [1], "1848024236": [1], "1848177019": [1], "1848667276": [1], "1848851066": [1], "1848888197": [1], "1848955732": [1], "1849171563": [1], "1849237577": [1], "1849475381": [1], "1849565818": [1], "1849695115": [1], "1849752441": [1], "1850774713": [1], "1850813852": [1], "1850943696": [1], "1851005760": [1], "1851899851": [1], "1852124662": [1], "1852285127": [1], "1852507471": [1], "1852686106": [1], "1852906882": [1], "1853234459": [1], "1853386756": [1], "1853438806": [1], "1853548864": [1], "1853612766": [1], "1854518558": [1], "1855192468": [1], "1855230110": [1], "1855370808": [1], "1855438000": [1], "1855497368": [1], "1855677163": [1], "1855984314": [1], "1856120916": [1], "1856687899": [1], "1857221231": [1], "1857504723": [1], "1858231000": [1], "1858251674": [1], "1858307449": [1], "1858354204": [1], "1858421927": [1], "1858806953": [1], "1858946668": [1], "1859168203": [1], "1859302783": [1], "1860005326": [1], "1860114256": [1], "1860219762": [1], "1860299149": [1], "1860463889": [1], "1860659987": [1], "1860833993": [1], "1860857172": [1], "1861074418": [1], "1861518533": [1], "1861842574": [1], "1862041357": [1], "1862043375": [1], "1862137221": [1], "1862145918": [1], "1862267708": [1], "1862626272": [1], "1862669775": [1], "1862834905": [1], "1862923089": [1], "1863697572": [1], "1863827748": [1], "1864444130": [1], "1864595893": [1], "1864649418": [1], "1865001175": [1], "1865288549": [1], "1865472442": [1], "1865617839": [1], "1865656768": [1], "1865741038": [1], "1865993724": [1], "1865995984": [1], "1866282226": [1], "1866343252": [1], "1866418844": [1], "1866571708": [1], "1866685668": [1], "1866825867": [1], "1867151395": [1], "1867240653": [1], "1867382313": [1], "1867511828": [1], "1867707710": [1], "1867771991": [1], "1868228984": [1], "1868839403": [1], "1869308495": [1], "1869360110": [1], "1869546037": [1], "1869550926": [1], "1870169280": [1], "1870506675": [1], "1870536237": [1], "1870819488": [1], "1871172238": [1], "1871245463": [1], "1871318646": [1], "1871374219": [1], "1871524392": [1], "1871711547": [1], "1871979922": [1], "1872150252": [1], "1872249545": [1], "1872823178": [1], "1872880928": [1], "1873092782": [1], "1873169715": [1], "1873478126": [1], "1874225087": [1], "1875052895": [1], "1875322697": [1], "1875370799": [1], "1875449450": [1], "1875496815": [1], "1875703765": [1], "1875770690": [1], "1875933490": [1], "1876314407": [1], "1876439036": [1], "1876615183": [1], "1876741368": [1], "1877057824": [1], "1877137730": [1], "1877755295": [1], "1878258828": [1], "1878315049": [1], "1878598249": [1], "1878641771": [1], "1878714766": [1], "1878793939": [1], "1878893420": [1], "1879197774": [1], "1879959759": [1], "1879960070": [1], "1879978132": [1], "1880202337": [1], "1880963465": [1], "1881152561": [1], "1881177118": [1], "1881288070": [1], "1881672815": [1], "1882170670": [1], "1882545585": [1], "1883019630": [1], "1883309183": [1], "1883378416": [1], "1883880120": [1], "1883966465": [1], "1884953792": [1], "1885086930": [1], "1885104388": [1], "1885526996": [1], "1885689552": [1], "1885802266": [1], "1886100862": [1], "1886353852": [1], "1886439343": [1], "1887041537": [1], "1887376484": [1], "1888172012": [1], "1888442171": [1], "1888557552": [1], "1888828716": [1], "1888842726": [1], "1888969761": [1], "1889243674": [1], "1889319516": [1], "1889358731": [1], "1890256785": [1], "1890644289": [1], "1891076117": [1], "1891134761": [1], "1891473039": [1], "1891601665": [1], "1891611238": [1], "1891693677": [1], "1891832950": [1], "1891914620": [1], "1891973174": [1], "1892123075": [1], "1892443473": [1], "1892522681": [1], "1892523781": [1], "1892590738": [1], "1892794187": [1], "1892847876": [1], "1893118800": [1], "1893213074": [1], "1893306501": [1], "1893313210": [1], "1893665469": [1], "1893746109": [1], "1893850500": [1], "1894197870": [1], "1894211546": [1], "1894296635": [1], "1894481502": [1], "1894667844": [1], "1894678918": [1], "1895343741": [1], "1895809176": [1], "1895919607": [1], "1895945237": [1], "1895962559": [1], "1896393752": [1], "1896886842": [1], "1897200323": [1], "1898060799": [1], "1898199383": [1], "1898794071": [1], "1899147319": [1], "1899531767": [1], "1900286022": [1], "1900611773": [1], "1900693320": [1], "1901140508": [1], "1901862048": [1], "1902415893": [1], "1902578338": [1], "1902879551": [1], "1902947618": [1], "1903284468": [1], "1903492967": [1], "1903668317": [1], "1903755489": [1], "1904216530": [1], "1904620682": [1], "1904623097": [1], "1904634617": [1], "1905029169": [1], "1905228883": [1], "1905415424": [1], "1905785420": [1], "1905897567": [1], "1906094024": [1], "1906135629": [1], "1906686782": [1], "1907187930": [1], "1907215601": [1], "1907368739": [1], "1908171086": [1], "1908891721": [1], "1909475520": [1], "1909524124": [1], "1909583067": [1], "1909731107": [1], "1909757883": [1], "1909852405": [1], "1909920638": [1], "1909925584": [1], "1910227469": [1], "1910712018": [1], "1910884965": [1], "1911561553": [1], "1911694337": [1], "1912059660": [1], "1912145550": [1], "1912321099": [1], "1913331444": [1], "1913332607": [1], "1913436922": [1], "1913976819": [1], "1914179756": [1], "1915035443": [1], "1915271621": [1], "1915742101": [1], "1917020144": [1], "1917285760": [1], "1917459890": [1], "1917862585": [1], "1917867597": [1], "1917918617": [1], "1918291028": [1], "1918481974": [1], "1918638909": [1], "1918796226": [1], "1918797354": [1], "1919400828": [1], "1919583896": [1], "1919721965": [1], "1919801347": [1], "1919875912": [1], "1920127434": [1], "1920360515": [1], "1920654508": [1], "1920829209": [1], "1921211474": [1], "1921647468": [1], "1922025122": [1], "1922302786": [1], "1922351441": [1], "1922474750": [1], "1922775549": [1], "1922812394": [1], "1922825375": [1], "1922962990": [1], "1923515162": [1], "1923641365": [1], "1925280364": [1], "1925290847": [1], "1925466289": [1], "1925740036": [1], "1926010573": [1], "1926011466": [1], "1926477203": [1], "1926737385": [1], "1926853035": [1], "1926963571": [1], "1927427634": [1], "1928018682": [1], "1928297486": [1], "1928639716": [1], "1928824369": [1], "1928930330": [1], "1929406693": [1], "1929430977": [1], "1929544513": [1], "1929735752": [1], "1929948980": [1], "1930616508": [1], "1931279505": [1], "1931463010": [1], "1931721934": [1], "1931743365": [1], "1931869057": [1], "1931952357": [1], "1932097054": [1], "1932446127": [1], "1932499913": [1], "1932724121": [1], "1932925652": [1], "1933808210": [1], "1934175365": [1], "1934458519": [1], "1934760967": [1], "1934973067": [1], "1935346847": [1], "1935406736": [1], "1935553715": [1], "1936466762": [1], "1936473986": [1], "1936816562": [1], "1936863426": [1], "1936883737": [1], "1937186982": [1], "1937194846": [1], "1937213584": [1], "1937242955": [1], "1937462317": [1], "1937601562": [1], "1937777162": [1], "1937785642": [1], "1938040576": [1], "1938146172": [1], "1938201291": [1], "1938240048": [1], "1938269495": [1], "1938655905": [1], "1939362382": [1], "1939663083": [1], "1939864463": [1], "1940409250": [1], "1940607042": [1], "1940752591": [1], "1940936341": [1], "1941437793": [1], "1941788960": [1], "1942156305": [1], "1942630382": [1], "1943481560": [1], "1943642415": [1], "1944085352": [1], "1944292160": [1], "1944551434": [1], "1944728841": [1], "1945796012": [1], "1946163695": [1], "1946277998": [1], "1946493511": [1], "1946738484": [1], "1947354592": [1], "1947433322": [1], "1948096821": [1], "1948108443": [1], "1948706540": [1], "1949324484": [1], "1949423561": [1], "1949932148": [1], "1950638997": [1], "1950810675": [1], "1951190890": [1], "1951272770": [1], "1951562760": [1], "1952386209": [1], "1952634826": [1], "1952679332": [1], "1952886682": [1], "1952894534": [1], "1952985399": [1], "1953196923": [1], "1953381862": [1], "1953404617": [1], "1953623675": [1], "1953813146": [1], "1954139942": [1], "1954273974": [1], "1954428990": [1], "1954658827": [1], "1954720401": [1], "1955586612": [1], "1955807237": [1], "1955909493": [1], "1956518514": [1], "1956705209": [1], "1957167353": [1], "1957183692": [1], "1957191975": [1], "1957277555": [1], "1957556023": [1], "1958256647": [1], "1958396542": [1], "1958473272": [1], "1958959575": [1], "1958977633": [1], "1959329669": [1], "1959737499": [1], "1959844846": [1], "1960428398": [1], "1960681987": [1], "1961037093": [1], "1961290059": [1], "1961359889": [1], "1961451215": [1], "1961731240": [1], "1961961808": [1], "1962289936": [1], "1962392750": [1], "1962775794": [1], "1963361524": [1], "1963762415": [1], "1964304238": [1], "1964311195": [1], "1964350775": [1], "1964916516": [1], "1965480379": [1], "1965501609": [1], "1965848141": [1], "1966315377": [1], "1966359946": [1], "1966704630": [1], "1966798447": [1], "1966986689": [1], "1967160163": [1], "1967245579": [1], "1967343997": [1], "1967489476": [1], "1967647944": [1], "1967993566": [1], "1968011981": [1], "1968216256": [1], "1968767891": [1], "1968948816": [1], "1968952120": [1], "1968996517": [1], "1969069289": [1], "1969321986": [1], "1969347393": [1], "1969618075": [1], "1969713665": [1], "1969814538": [1], "1970027158": [1], "1970058034": [1], "1970458937": [1], "1970817528": [1], "1970851930": [1], "1971358366": [1], "1971364134": [1], "1971378720": [1], "1971446404": [1], "1971827805": [1], "1972009646": [1], "1972072761": [1], "1972211923": [1], "1972391637": [1], "1972590708": [1], "1972642437": [1], "1972656271": [1], "1972797485": [1], "1973049232": [1], "1973512095": [1], "1973706571": [1], "1973762765": [1], "1974010169": [1], "1974077857": [1], "1974651059": [1], "1974714665": [1], "1974869048": [1], "1975242476": [1], "1975377179": [1], "1975459622": [1], "1975644275": [1], "1975789715": [1], "1975829497": [1], "1975843954": [1], "1976397755": [1], "1976909549": [1], "1977095138": [1], "1977162411": [1], "1977306010": [1], "1977375096": [1], "1977450236": [1], "1977679224": [1], "1977882545": [1], "1978183916": [1], "1978233078": [1], "1978423311": [1], "1978527579": [1], "1978936756": [1], "1978976337": [1], "1979139687": [1], "1979602018": [1], "1979640142": [1], "1980194991": [1], "1980374221": [1], "1980776124": [1], "1980969336": [1], "1980981939": [1], "1981008706": [1], "1981011029": [1], "1981223408": [1], "1981255797": [1], "1981442185": [1], "1981864146": [1], "1982461630": [1], "1983084428": [1], "1983095172": [1], "1983842332": [1], "1984128916": [1], "1984410983": [1], "1984822558": [1], "1985256322": [1], "1985933927": [1], "1985998899": [1], "1986654607": [1], "1986685285": [1], "1986738780": [1], "1986809101": [1], "1986857272": [1], "1987248800": [1], "1987926884": [1], "1987975486": [1], "1988124741": [1], "1988305505": [1], "1988687969": [1], "1989114553": [1], "1989371608": [1], "1989444438": [1], "1989626993": [1], "1989677569": [1], "1989783209": [1], "1990102109": [1], "1990486009": [1], "1990486620": [1], "1990672484": [1], "1991067186": [1], "1991465212": [1], "1991702841": [1], "1991869542": [1], "1991893238": [1], "1992106942": [1], "1992396206": [1], "1992584243": [1], "1992767929": [1], "1993141613": [1], "1993152456": [1], "1993322246": [1], "1993437187": [1], "1993903323": [1], "1993990431": [1], "1994132577": [1], "1994425820": [1], "1994842388": [1], "1994984465": [1], "1995152187": [1], "1995450754": [1], "1995500945": [1], "1995795432": [1], "1995845622": [1], "1996038155": [1], "1996123742": [1], "1996278098": [1], "1996679291": [1], "1996721096": [1], "1996907753": [1], "1997146363": [1], "1997793565": [1], "1997808689": [1], "1997850672": [1], "1998293805": [1], "1998497718": [1], "1998632552": [1], "1998771304": [1], "1998812545": [1], "1999011992": [1], "1999300160": [1], "1999603665": [1], "1999760479": [1], "1999806303": [1], "1999824246": [1], "1999958138": [1], "2000041153": [1], "2000174958": [1], "2000352033": [1], "2000377829": [1], "2001160447": [1], "2001231139": [1], "2001369130": [1], "2001468129": [1], "2001487746": [1], "2001937266": [1], "2001974146": [1], "2002321410": [1], "2002447057": [1], "2002475287": [1], "2002918254": [1], "2003600151": [1], "2003645717": [1], "2003706407": [1], "2004036972": [1], "2005023973": [1], "2005072584": [1], "2005199380": [1], "2005355499": [1], "2006912118": [1], "2006915087": [1], "2006931686": [1], "2007029243": [1], "2007115111": [1], "2007275179": [1], "2007294549": [1], "2007736849": [1], "2008512983": [1], "2008581869": [1], "2008756500": [1], "2009525397": [1], "2009686645": [1], "2009714229": [1], "2010360529": [1], "2010392512": [1], "2010641171": [1], "2010857451": [1], "2011464736": [1], "2011638302": [1], "2011886971": [1], "2011910081": [1], "2012044969": [1], "2012063290": [1], "2012391721": [1], "2012394180": [1], "2012425057": [1], "2012501604": [1], "2012891300": [1], "2013240385": [1], "2013309557": [1], "2013940632": [1], "2014481952": [1], "2014723133": [1], "2014943931": [1], "2015642505": [1], "2015734346": [1], "2015749590": [1], "2016237729": [1], "2016601497": [1], "2016921180": [1], "2017352288": [1], "2017359027": [1], "2017479430": [1], "2017628569": [1], "2017720328": [1], "2018372495": [1], "2018993768": [1], "2019116010": [1], "2019351759": [1], "2020068129": [1], "2020170201": [1], "2020518699": [1], "2021069191": [1], "2021370933": [1], "2021860533": [1], "2021893696": [1], "2022008378": [1], "2022131570": [1], "2022266645": [1], "2022330928": [1], "2022561620": [1], "2022812484": [1], "2022914789": [1], "2023091160": [1], "2023092406": [1], "2023728829": [1], "2023839301": [1], "2024088148": [1], "2024116575": [1], "2025083538": [1], "2025286758": [1], "2025312867": [1], "2025694591": [1], "2025817863": [1], "2025897681": [1], "2026165662": [1], "2026419546": [1], "2026503351": [1], "2026964547": [1], "2027416794": [1], "2027626935": [1], "2027839933": [1], "2027938920": [1], "2028627353": [1], "2028676505": [1], "2028760130": [1], "2028940002": [1], "2029058639": [1], "2029101658": [1], "2029902512": [1], "2029907937": [1], "2029964863": [1], "2029990774": [1], "2030203259": [1], "2030612629": [1], "2030652630": [1], "2030965798": [1], "2031539502": [1], "2032904832": [1], "2032927450": [1], "2033166303": [1], "2033391370": [1], "2033902275": [1], "2034133936": [1], "2034151409": [1], "2034854593": [1], "2035042453": [1], "2035600660": [1], "2035868431": [1], "2035951895": [1], "2036949401": [1], "2037238619": [1], "2037524848": [1], "2037531237": [1], "2037546839": [1], "2037654146": [1], "2037893305": [1], "2037940607": [1], "2038073953": [1], "2038293587": [1], "2038571448": [1], "2038908953": [1], "2039478130": [1], "2039553671": [1], "2039735007": [1], "2039740510": [1], "2039827958": [1], "2039986714": [1], "2040033014": [1], "2040305266": [1], "2040615996": [1], "2040952592": [1], "2041523340": [1], "2041551907": [1], "2042214781": [1], "2042373885": [1], "2042850797": [1], "2043070984": [1], "2043480204": [1], "2043510314": [1], "2043892436": [1], "2043935568": [1], "2045344399": [1], "2045347884": [1], "2045636363": [1], "2045706236": [1], "2045969039": [1], "2046187786": [1], "2046257463": [1], "2046353527": [1], "2046403536": [1], "2046844005": [1], "2047096758": [1], "2047153072": [1], "2047221573": [1], "2047401783": [1], "2047480308": [1], "2047842876": [1], "2048651980": [1], "2048682331": [1], "2048814841": [1], "2048817369": [1], "2048985443": [1], "2049192084": [1], "2049237050": [1], "2049433607": [1], "2049503826": [1], "2049600555": [1], "2050086393": [1], "2050552970": [1], "2051084552": [1], "2051445648": [1], "2051678551": [1], "2051954974": [1], "2052813967": [1], "2053105352": [1], "2053587384": [1], "2053767132": [1], "2053834098": [1], "2054133042": [1], "2054194366": [1], "2054681553": [1], "2054705763": [1], "2054836175": [1], "2054907250": [1], "2055418078": [1], "2055581632": [1], "2055586551": [1], "2055813378": [1], "2055842215": [1], "2055880494": [1], "2056217468": [1], "2056299493": [1], "2056318180": [1], "2056407534": [1], "2056785371": [1], "2056899192": [1], "2057116224": [1], "2057218843": [1], "2057671664": [1], "2057727506": [1], "2057732187": [1], "2057798640": [1], "2057980379": [1], "2058095539": [1], "2059050922": [1], "2059607139": [1], "2059804041": [1], "2060208057": [1], "2060431439": [1], "2060799009": [1], "2061120426": [1], "2061129144": [1], "2061172951": [1], "2061562820": [1], "2061705261": [1], "2061868712": [1], "2062077567": [1], "2062121270": [1], "2062126348": [1], "2062316065": [1], "2062355256": [1], "2062720548": [1], "2062724588": [1], "2062765814": [1], "2062894426": [1], "2063255796": [1], "2063393888": [1], "2063511622": [1], "2063700030": [1], "2064005671": [1], "2064218423": [1], "2064241041": [1], "2064258925": [1], "2064525878": [1], "2064556328": [1], "2064682483": [1], "2064851700": [1], "2065120126": [1], "2066078047": [1], "2066716707": [1], "2066799957": [1], "2067512419": [1], "2068209776": [1], "2068218166": [1], "2068297898": [1], "2068565097": [1], "2068643972": [1], "2068733038": [1], "2069185191": [1], "2069389938": [1], "2069725387": [1], "2070011324": [1], "2070125916": [1], "2070311689": [1], "2071404650": [1], "2071871513": [1], "2072596639": [1], "2072684566": [1], "2072804053": [1], "2072840139": [1], "2072857293": [1], "2072914240": [1], "2073480556": [1], "2073483301": [1], "2074069419": [1], "2074158716": [1], "2074687179": [1], "2074988730": [1], "2075257762": [1], "2075346061": [1], "2075486055": [1], "2075594956": [1], "2075708831": [1], "2075824271": [1], "2076001938": [1], "2076573554": [1], "2076962925": [1], "2076974035": [1], "2077443955": [1], "2078311650": [1], "2078552158": [1], "2078654055": [1], "2078725686": [1], "2078781324": [1], "2078803849": [1], "2079094967": [1], "2079213442": [1], "2079433685": [1], "2079495017": [1], "2080367385": [1], "2080651269": [1], "2080653109": [1], "2080796509": [1], "2080825286": [1], "2081275578": [1], "2081350169": [1], "2081558848": [1], "2081941299": [1], "2082023531": [1], "2082270168": [1], "2082987184": [1], "2082997895": [1], "2083947380": [1], "2084231778": [1], "2084377201": [1], "2085023729": [1], "2085177070": [1], "2085247838": [1], "2085564593": [1], "2085891632": [1], "2086185676": [1], "2086284780": [1], "2086448185": [1], "2086895395": [1], "2087829145": [1], "2088451487": [1], "2088470766": [1], "2089685891": [1], "2089744488": [1], "2089853735": [1], "2089936579": [1], "2090003937": [1], "2090362715": [1], "2090826753": [1], "2090987762": [1], "2091087869": [1], "2091128422": [1], "2091152875": [1], "2091467863": [1], "2091481100": [1], "2091890663": [1], "2092397452": [1], "2092514951": [1], "2092519729": [1], "2092624117": [1], "2092630208": [1], "2092935924": [1], "2093185671": [1], "2093505679": [1], "2094207587": [1], "2094350886": [1], "2094414634": [1], "2094587446": [1], "2095052789": [1], "2095524097": [1], "2096116625": [1], "2096607763": [1], "2096635041": [1], "2096925586": [1], "2097174465": [1], "2097216490": [1], "2097235337": [1], "2097439377": [1], "2097548359": [1], "2098132172": [1], "2098335008": [1], "2098426150": [1], "2098645800": [1], "2098971440": [1], "2100248965": [1], "2100285249": [1], "2100287459": [1], "2100300212": [1], "2100598395": [1], "2100598956": [1], "2101231048": [1], "2101485456": [1], "2101830516": [1], "2102061800": [1], "2102450486": [1], "2102503575": [1], "2102518900": [1], "2102764928": [1], "2103063416": [1], "2103081120": [1], "2103575230": [1], "2103983012": [1], "2103998845": [1], "2104360530": [1], "2104809719": [1], "2104841360": [1], "2105226605": [1], "2105296437": [1], "2105562737": [1], "2106033497": [1], "2106302237": [1], "2106334233": [1], "2106377384": [1], "2106433173": [1], "2107080625": [1], "2107503392": [1], "2107900584": [1], "2108538687": [1], "2108564348": [1], "2109069089": [1], "2109118288": [1], "2109126820": [1], "2109193299": [1], "2109301240": [1], "2109377434": [1], "2109538623": [1], "2109690647": [1], "2109839097": [1], "2110230167": [1], "2110633757": [1], "2111002018": [1], "2112307468": [1], "2112483985": [1], "2112913210": [1], "2112915449": [1], "2112919708": [1], "2113086424": [1], "2113121766": [1], "2113269557": [1], "2113494878": [1], "2113864540": [1], "2114094733": [1], "2114509404": [1], "2114745670": [1], "2114792361": [1], "2114914452": [1], "2115389524": [1], "2115564258": [1], "2115627472": [1], "2116055653": [1], "2116696564": [1], "2116903872": [1], "2116914355": [1], "2117054738": [1], "2117081596": [1], "2117198122": [1], "2117398976": [1], "2117538586": [1], "2118297306": [1], "2118528652": [1], "2118697622": [1], "2118856858": [1], "2118900105": [1], "2119009574": [1], "2119213605": [1], "2119394213": [1], "2119723882": [1], "2119924234": [1], "2120040924": [1], "2120051143": [1], "2120221285": [1], "2121150757": [1], "2121197739": [1], "2122014066": [1], "2122199282": [1], "2122228970": [1], "2122489886": [1], "2122586611": [1], "2122616018": [1], "2123589494": [1], "2123780144": [1], "2123808272": [1], "2124234719": [1], "2124581745": [1], "2124747906": [1], "2124796333": [1], "2125035188": [1], "2125041812": [1], "2125079672": [1], "2125093435": [1], "2125276444": [1], "2125543146": [1], "2125612826": [1], "2126013408": [1], "2126286708": [1], "2126288801": [1], "2126598686": [1], "2126712783": [1], "2126756783": [1], "2127724644": [1], "2127936809": [1], "2128056411": [1], "2128058759": [1], "2128172412": [1], "2129317152": [1], "2129900312": [1], "2130023780": [1], "2130627394": [1], "2130867980": [1], "2131043084": [1], "2131452599": [1], "2131665205": [1], "2131786304": [1], "2131844464": [1], "2131941799": [1], "2132093137": [1], "2132415333": [1], "2132979146": [1], "2133131401": [1], "2133188739": [1], "2133354654": [1], "2133392499": [1], "2133448405": [1], "2133458686": [1], "2133487634": [1], "2133734946": [1], "2134389409": [1], "2134724644": [1], "2134889089": [1], "2135152917": [1], "2135205642": [1], "2135744331": [1], "2135752791": [1], "2135802817": [1], "2136150346": [1], "2137062190": [1], "2137163172": [1], "2137190467": [1], "2137460427": [1], "2137726037": [1], "2138140159": [1], "2138537726": [1], "2138601301": [1], "2139509731": [1], "2139517217": [1], "2140335082": [1], "2140660332": [1], "2140934070": [1], "2141531399": [1], "2141620489": [1], "2141679926": [1], "2142430174": [1], "2142520268": [1], "2143331426": [1], "2143789725": [1], "2143815146": [1], "2144228550": [1], "2144445010": [1], "2144731341": [1], "2144788535": [1], "2144944633": [1], "2145097875": [1], "2145101307": [1], "2145402105": [1], "2145950221": [1], "2146923977": [1], "2146954744": [1], "2147094670": [1], "2147109152": [1], "2147476872": [1]}, "references": [{"name": "ebola-sudan", "length": 18875, "nMinimizers": 9481}, {"name": "ebola-zaire", "length": 18959, "nMinimizers": 9443}], "normalization": [1.9908237527686954, 2.007730594090861]} \ No newline at end of file diff --git a/preprocessing/nextclade/tests/multi_segment_config.yaml b/preprocessing/nextclade/tests/multi_segment_config.yaml index ea1437ca12..f92c01a222 100644 --- a/preprocessing/nextclade/tests/multi_segment_config.yaml +++ b/preprocessing/nextclade/tests/multi_segment_config.yaml @@ -6,11 +6,9 @@ segment_classification_method: "minimizer" nucleotideSequences: - name: ebola-sudan nextclade_dataset_name: ebola-dataset/ebola-sudan - accepted_sort_matches: [accepted-name] genes: [NPEbolaSudan, VP35EbolaSudan, LEbolaSudan] - name: ebola-zaire nextclade_dataset_name: ebola-dataset/ebola-zaire - accepted_sort_matches: [accepted-name] genes: [VP24EbolaZaire, LEbolaZaire] organism: multi-ebola-test processing_spec: diff --git a/preprocessing/nextclade/tests/single_segment_config.yaml b/preprocessing/nextclade/tests/single_segment_config.yaml index 41ec8c4380..2ecb88bc3e 100644 --- a/preprocessing/nextclade/tests/single_segment_config.yaml +++ b/preprocessing/nextclade/tests/single_segment_config.yaml @@ -7,7 +7,7 @@ topology: "linear" db_name: "Loculus" minimizer_index: TEST nucleotideSequences: -- nextclade_dataset_name: ebola-sudan-test-dataset +- nextclade_dataset_name: ebola-sudan genes: [NPEbolaSudan, VP35EbolaSudan, LEbolaSudan] organism: ebola-sudan-test processing_spec: diff --git a/preprocessing/nextclade/tests/test_nextclade_preprocessing.py b/preprocessing/nextclade/tests/test_nextclade_preprocessing.py index 32e3de184d..24e3c5f1bf 100644 --- a/preprocessing/nextclade/tests/test_nextclade_preprocessing.py +++ b/preprocessing/nextclade/tests/test_nextclade_preprocessing.py @@ -202,6 +202,9 @@ def invalid_sequence() -> str: sequenceNameToFastaId={"main": "fastaHeader"}, ), ), +] + +single_segment_failed_case_definitions = [ Case( name="with failed alignment", input_metadata={}, @@ -237,6 +240,85 @@ def invalid_sequence() -> str: ), ] +single_segment_failed_with_require_sort_case_definitions = [ + Case( + name="with failed alignment", + input_metadata={}, + input_sequence={"fastaHeader": invalid_sequence()}, + accession_id="1", + expected_metadata={ + "completeness": None, + "totalInsertedNucs": None, + "totalSnps": None, + "totalDeletedNucs": None, + "length": len(invalid_sequence()), + }, + expected_errors=build_processing_annotations( + [ + ProcessingAnnotationHelper( + ["main"], + ["main"], + "Nucleotide sequence failed to align", + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + ), + ] + ), + expected_warnings=build_processing_annotations( + [ + ProcessingAnnotationHelper.sequence_annotation_helper( + "Sequence does not appear to match reference, per `nextclade sort`. " + "Double check you are submitting to the correct organism.", + ), + ] + ), + expected_processed_alignment=ProcessedAlignment( + unalignedNucleotideSequences={"main": invalid_sequence()}, + alignedNucleotideSequences={}, + nucleotideInsertions={}, + alignedAminoAcidSequences={}, + aminoAcidInsertions={}, + sequenceNameToFastaId={"main": "fastaHeader"}, + ), + ), + Case( + name="with better alignment", + input_metadata={}, + input_sequence={"fastaHeader": consensus_sequence("ebola-zaire")}, + accession_id="1", + expected_metadata={ + "completeness": None, + "totalInsertedNucs": None, + "totalSnps": None, + "totalDeletedNucs": None, + "length": len(consensus_sequence("ebola-zaire")), + }, + expected_errors=build_processing_annotations( + [ + ProcessingAnnotationHelper( + ["main"], + ["main"], + "Nucleotide sequence failed to align", + AnnotationSourceType.NUCLEOTIDE_SEQUENCE, + ), + ProcessingAnnotationHelper.sequence_annotation_helper( + "Sequence best matches ebola-zaire, a different organism than the one " + "you are submitting to: ebola-sudan-test. It is therefore not possible " + "to release. Contact the administrator if you think this message is an error.", + ), + ] + ), + expected_warnings=[], + expected_processed_alignment=ProcessedAlignment( + unalignedNucleotideSequences={"main": consensus_sequence("ebola-zaire")}, + alignedNucleotideSequences={}, + nucleotideInsertions={}, + alignedAminoAcidSequences={}, + aminoAcidInsertions={}, + sequenceNameToFastaId={"main": "fastaHeader"}, + ), + ), +] + multi_segment_case_definitions = [ Case( name="with mutation", @@ -439,7 +521,7 @@ def invalid_sequence() -> str: expected_errors=build_processing_annotations( [ ProcessingAnnotationHelper.sequence_annotation_helper( - "Sequence with fasta header fastaHeader1 does not align to any segment for " + "Sequence with fasta id fastaHeader1 does not match any reference for " "organism: multi-ebola-test per `nextclade align`. " "Double check you are submitting to the correct organism." ) @@ -476,7 +558,7 @@ def invalid_sequence() -> str: expected_errors=build_processing_annotations( [ ProcessingAnnotationHelper.sequence_annotation_helper( - "Sequence with fasta header fastaHeader1 does not align to any segment for " + "Sequence with fasta id fastaHeader1 does not match any reference for " "organism: multi-ebola-test per `nextclade align`. " "Double check you are submitting to the correct organism." ) @@ -520,7 +602,7 @@ def invalid_sequence() -> str: expected_errors=build_processing_annotations( [ ProcessingAnnotationHelper.sequence_annotation_helper( - "Sequence with fasta header fastaHeader1 does not appear to match any reference" + "Sequence with fasta id fastaHeader1 does not match any reference" " for organism: multi-ebola-test per `nextclade sort`. " "Double check you are submitting to the correct organism.", ) @@ -557,7 +639,7 @@ def invalid_sequence() -> str: expected_errors=build_processing_annotations( [ ProcessingAnnotationHelper.sequence_annotation_helper( - "Sequence with fasta header fastaHeader1 does not appear to match any reference " + "Sequence with fasta id fastaHeader1 does not match any reference " "for organism: multi-ebola-test per `nextclade sort`. " "Double check you are submitting to the correct organism." ) @@ -609,7 +691,7 @@ def invalid_sequence() -> str: expected_warnings=build_processing_annotations( [ ProcessingAnnotationHelper.sequence_annotation_helper( - "Sequence with fasta header fastaHeader1 does not appear to match any reference " + "Sequence with fasta id fastaHeader1 does not match any reference " "for organism: multi-ebola-test per `nextclade sort`. " "Double check you are submitting to the correct organism.", ) @@ -646,7 +728,7 @@ def invalid_sequence() -> str: expected_warnings=build_processing_annotations( [ ProcessingAnnotationHelper.sequence_annotation_helper( - "Sequence with fasta header fastaHeader1 does not appear to match any reference" + "Sequence with fasta id fastaHeader1 does not match any reference" " for organism: multi-ebola-test per `nextclade sort`. " "Double check you are submitting to the correct organism.", ) @@ -697,7 +779,7 @@ def invalid_sequence() -> str: expected_warnings=build_processing_annotations( [ ProcessingAnnotationHelper.sequence_annotation_helper( - "Sequence with fasta header fastaHeader1 does not align to any segment for " + "Sequence with fasta id fastaHeader1 does not match any reference for " "organism: multi-ebola-test per `nextclade align`. " "Double check you are submitting to the correct organism.", ) @@ -734,7 +816,7 @@ def invalid_sequence() -> str: expected_warnings=build_processing_annotations( [ ProcessingAnnotationHelper.sequence_annotation_helper( - "Sequence with fasta header fastaHeader1 does not align to any segment for " + "Sequence with fasta id fastaHeader1 does not match any reference for " "organism: multi-ebola-test per `nextclade align`. " "Double check you are submitting to the correct organism.", ) @@ -814,7 +896,7 @@ def invalid_sequence() -> str: expected_errors=build_processing_annotations( [ ProcessingAnnotationHelper.sequence_annotation_helper( - "Multiple sequences (with fasta headers: duplicate_ebola-sudan, ebola-sudan) " + "Multiple sequences (with fasta ids: ebola-sudan, duplicate_ebola-sudan) " "align to ebola-sudan - only one entry is allowed.", ), ] @@ -935,7 +1017,9 @@ def process_single_entry( @pytest.mark.parametrize( "test_case_def", - single_segment_case_definitions + segment_validation_tests_single_segment, + single_segment_case_definitions + + segment_validation_tests_single_segment + + single_segment_failed_case_definitions, ids=lambda tc: f"single segment {tc.name}", ) def test_preprocessing_single_segment(test_case_def: Case): @@ -948,6 +1032,24 @@ def test_preprocessing_single_segment(test_case_def: Case): ) +@pytest.mark.parametrize( + "test_case_def", + single_segment_case_definitions + + segment_validation_tests_single_segment + + single_segment_failed_with_require_sort_case_definitions, + ids=lambda tc: f"single segment with require_nextclade_sort_match {tc.name}", +) +def test_preprocessing_single_segment_with_require_nextclade_sort_match(test_case_def: Case): + config = get_config(SINGLE_SEGMENT_CONFIG, ignore_args=True) + config.require_nextclade_sort_match = True + factory_custom = ProcessedEntryFactory(all_metadata_fields=list(config.processing_spec.keys())) + test_case = test_case_def.create_test_case(factory_custom) + processed_entry = process_single_entry(test_case, config, EBOLA_SUDAN_DATASET) + verify_processed_entry( + processed_entry.processed_entry, test_case.expected_output, test_case.name + ) + + @pytest.mark.parametrize( "test_case_def", multi_segment_case_definitions @@ -1149,7 +1251,8 @@ def multiple_valid_segments_error(metadata_name: str) -> ProcessingAnnotation: AnnotationSource(name="ebola-zaire", type=AnnotationSourceType.NUCLEOTIDE_SEQUENCE), ], processedFields=[AnnotationSource(name=metadata_name, type=AnnotationSourceType.METADATA)], - message="Organism multi-ebola-test is configured to only accept one segment per submission, found multiple valid segments: ['ebola-sudan', 'ebola-zaire'].", + message="Organism multi-ebola-test is configured to only accept one segment per submission," + " found multiple valid segments: ['ebola-sudan', 'ebola-zaire'].", ) From 1eb679b9f653862f3e3360b79829f94d19baac09 Mon Sep 17 00:00:00 2001 From: "Anna (Anya) Parker" <50943381+anna-parker@users.noreply.github.com> Date: Mon, 1 Dec 2025 16:09:25 +0100 Subject: [PATCH 31/44] Apply suggestions from code review Co-authored-by: Fabian Engelniederhammer <92720311+fengelniederhammer@users.noreply.github.com> Co-authored-by: Cornelius Roemer --- .../src/main/kotlin/org/loculus/backend/model/SubmitModel.kt | 2 +- website/src/config.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt b/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt index 7f667d81e6..9af8639c1a 100644 --- a/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt +++ b/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt @@ -356,7 +356,7 @@ class SubmitModel( "Metadata file contains ${metadataKeysNotInSequences.size} FASTA ids that are not present " + "in the sequence file: " + metadataKeysNotInSequences.toList().joinToString(limit = 10) { "'$it'" - } + } + ". " } else { "" } diff --git a/website/src/config.ts b/website/src/config.ts index b60ff003b0..a869487f94 100644 --- a/website/src/config.ts +++ b/website/src/config.ts @@ -192,7 +192,7 @@ export function getSubmissionIdInputFields(isMultiSegmented: boolean): InputFiel displayName: 'ID', definition: 'FASTA ID', guidance: - 'Your sequence identifier; should match the FASTA file header - this is used to link the metadata to the FASTA sequence.', + "Your sequence identifier; should match the sequence's id in the FASTA file - this is used to link the metadata to the FASTA sequence.", example: 'GJP123', noEdit: true, required: true, @@ -205,7 +205,7 @@ export function getSubmissionIdInputFields(isMultiSegmented: boolean): InputFiel displayName: 'ID', definition: 'METADATA ID', guidance: - 'Your sample identifier. If no column with FASTA IDS is provided, this ID will be used to associate the metadata with the sequence.', + 'Your sample identifier. If FASTA IDS column is provided, this sample ID will be used to associate the metadata with the sequence.', example: 'GJP123', noEdit: true, required: true, From 8f649d84a25a3d72e7db399bf9d4f803543e6360 Mon Sep 17 00:00:00 2001 From: anna-parker <50943381+anna-parker@users.noreply.github.com> Date: Mon, 1 Dec 2025 17:50:52 +0100 Subject: [PATCH 32/44] weird merge conflict --- .../loculus/backend/service/submission/CompressionService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/kotlin/org/loculus/backend/service/submission/CompressionService.kt b/backend/src/main/kotlin/org/loculus/backend/service/submission/CompressionService.kt index 1ebd5b8e1c..978c889f31 100644 --- a/backend/src/main/kotlin/org/loculus/backend/service/submission/CompressionService.kt +++ b/backend/src/main/kotlin/org/loculus/backend/service/submission/CompressionService.kt @@ -102,7 +102,7 @@ class CompressionService(private val compressionDictService: CompressionDictServ } }, processedData.aminoAcidInsertions, - processedData.sequenceNameToFastaId, + processedData.sequenceNameToFastaId, processedData.files, ) From 3ce662c4e9654d9cfc706632ff1c9670f55270eb Mon Sep 17 00:00:00 2001 From: Cornelius Roemer Date: Tue, 2 Dec 2025 12:42:44 +0100 Subject: [PATCH 33/44] Add maxSequencesPerEntry option and validate no duplicate fastaIds (#5559) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolves https://github.com/loculus-project/loculus/issues/5558 🚀 Preview: https://backend-reject-gt1-seq.loculus.org --------- Co-authored-by: anna-parker <50943381+anna-parker@users.noreply.github.com> Co-authored-by: Fabian Engelniederhammer <92720311+fengelniederhammer@users.noreply.github.com> Co-authored-by: Theo Sanderson --- backend/AGENTS.md | 8 +- .../org/loculus/backend/config/Config.kt | 4 +- .../loculus/backend/config/ReferenceGenome.kt | 2 +- .../org/loculus/backend/model/SubmitModel.kt | 13 +- .../submission/UploadDatabaseService.kt | 4 +- .../loculus/backend/utils/MetadataEntry.kt | 166 ++++++------ .../backend/utils/MetadataEntryTest.kt | 256 ++++++++++++++++-- .../templates/_submission-data-types.tpl | 9 +- kubernetes/loculus/values.schema.json | 6 + kubernetes/loculus/values.yaml | 8 + 10 files changed, 358 insertions(+), 118 deletions(-) diff --git a/backend/AGENTS.md b/backend/AGENTS.md index 9b9cd23fec..d1a85fe88d 100644 --- a/backend/AGENTS.md +++ b/backend/AGENTS.md @@ -1,6 +1,10 @@ Kotlin dependent packages have already been installed for you. -To run tests: +Run tests like this (if you have Docker set up properly): + +./gradlew test --console=plain + +If that doesn't work due to Docker issues because you're running inside a cloud environment, try this: USE_NONDOCKER_INFRA=true ./gradlew test --console=plain @@ -15,4 +19,4 @@ Always ensure the tests and lint pass before committing. Use conventional commits as titles for PRs, e.g. feat(deployment):xx, fix!(website):xx, chore(backend):xx. -Components include: website, backend, deployment, preprocessing, ingest, deposition. \ No newline at end of file +Components include: website, backend, deployment, preprocessing, ingest, deposition. diff --git a/backend/src/main/kotlin/org/loculus/backend/config/Config.kt b/backend/src/main/kotlin/org/loculus/backend/config/Config.kt index 4a7f8675cd..f55f4051d1 100644 --- a/backend/src/main/kotlin/org/loculus/backend/config/Config.kt +++ b/backend/src/main/kotlin/org/loculus/backend/config/Config.kt @@ -51,11 +51,13 @@ data class Schema( val externalMetadata: List = emptyList(), val earliestReleaseDate: EarliestReleaseDate = EarliestReleaseDate(false, emptyList()), val submissionDataTypes: SubmissionDataTypes = SubmissionDataTypes(), - val files: List = emptyList(), + val files: List = emptyList(), // Allowed file categories for output files ) data class SubmissionDataTypes( val consensusSequences: Boolean = true, + val maxSequencesPerEntry: Int? = null, // null means unlimited sequences per entry + // Allowed file categories for submission files val files: FilesSubmissionDataType = FilesSubmissionDataType(false, emptyList()), ) diff --git a/backend/src/main/kotlin/org/loculus/backend/config/ReferenceGenome.kt b/backend/src/main/kotlin/org/loculus/backend/config/ReferenceGenome.kt index edbc75ed9f..f745c3c8ae 100644 --- a/backend/src/main/kotlin/org/loculus/backend/config/ReferenceGenome.kt +++ b/backend/src/main/kotlin/org/loculus/backend/config/ReferenceGenome.kt @@ -24,7 +24,7 @@ data class ReferenceGenome(val nucleotideSequences: List, val ?.sequence private fun shortenSequence(sequence: String) = when { - sequence.length > 10 -> sequence.substring(0, 10) + "..." + sequence.length > 10 -> sequence.take(10) + "..." else -> sequence } diff --git a/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt b/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt index 9af8639c1a..0c0da96a5d 100644 --- a/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt +++ b/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt @@ -14,10 +14,7 @@ import org.loculus.backend.config.BackendConfig import org.loculus.backend.controller.BadRequestException import org.loculus.backend.controller.DuplicateKeyException import org.loculus.backend.controller.UnprocessableEntityException -import org.loculus.backend.service.datauseterms.DataUseTermsPreconditionValidator import org.loculus.backend.service.files.FilesDatabaseService -import org.loculus.backend.service.files.S3Service -import org.loculus.backend.service.groupmanagement.GroupManagementPreconditionValidator import org.loculus.backend.service.submission.CompressionAlgorithm import org.loculus.backend.service.submission.SubmissionIdFilesMappingPreconditionValidator import org.loculus.backend.service.submission.UploadDatabaseService @@ -88,7 +85,6 @@ class SubmitModel( private val submissionIdFilesMappingPreconditionValidator: SubmissionIdFilesMappingPreconditionValidator, private val dateProvider: DateProvider, private val backendConfig: BackendConfig, - private val s3Service: S3Service, ) { companion object AcceptedFileTypes { @@ -259,10 +255,15 @@ class SubmitModel( "from $submissionParams.submitter with UploadId $uploadId" } val now = dateProvider.getCurrentDateTime() + val maxSequencesPerEntry = backendConfig.getInstanceConfig(submissionParams.organism) + .schema + .submissionDataTypes + .maxSequencesPerEntry + try { when (submissionParams) { is SubmissionParams.OriginalSubmissionParams -> { - metadataEntryStreamAsSequence(metadataStream) + metadataEntryStreamAsSequence(metadataStream, maxSequencesPerEntry) .chunked(batchSize) .forEach { batch -> uploadDatabaseService.batchInsertMetadataInAuxTable( @@ -278,7 +279,7 @@ class SubmitModel( } is SubmissionParams.RevisionSubmissionParams -> { - revisionEntryStreamAsSequence(metadataStream) + revisionEntryStreamAsSequence(metadataStream, maxSequencesPerEntry) .chunked(batchSize) .forEach { batch -> uploadDatabaseService.batchInsertRevisedMetadataInAuxTable( diff --git a/backend/src/main/kotlin/org/loculus/backend/service/submission/UploadDatabaseService.kt b/backend/src/main/kotlin/org/loculus/backend/service/submission/UploadDatabaseService.kt index a568d412f7..df423ca127 100644 --- a/backend/src/main/kotlin/org/loculus/backend/service/submission/UploadDatabaseService.kt +++ b/backend/src/main/kotlin/org/loculus/backend/service/submission/UploadDatabaseService.kt @@ -79,7 +79,7 @@ class UploadDatabaseService( this[groupIdColumn] = groupId this[uploadedAtColumn] = uploadedAt this[submissionIdColumn] = it.submissionId - this[fastaIdsColumn] = it.fastaIds + this[fastaIdsColumn] = it.fastaIds?.toList() this[metadataColumn] = it.metadata this[filesColumn] = files?.get(it.submissionId) this[organismColumn] = submittedOrganism.name @@ -118,7 +118,7 @@ class UploadDatabaseService( this[submitterColumn] = authenticatedUser.username this[uploadedAtColumn] = uploadedAt this[submissionIdColumn] = it.submissionId - this[fastaIdsColumn] = it.fastaIds + this[fastaIdsColumn] = it.fastaIds?.toList() this[metadataColumn] = it.metadata this[filesColumn] = files?.get(it.submissionId) this[organismColumn] = submittedOrganism.name diff --git a/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt b/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt index d201d4a045..43a5431400 100644 --- a/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt +++ b/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt @@ -2,6 +2,7 @@ package org.loculus.backend.utils import org.apache.commons.csv.CSVException import org.apache.commons.csv.CSVFormat +import org.apache.commons.csv.CSVParser import org.apache.commons.csv.CSVRecord import org.loculus.backend.controller.UnprocessableEntityException import org.loculus.backend.model.ACCESSION_HEADER @@ -17,7 +18,7 @@ import java.io.InputStreamReader data class MetadataEntry( val submissionId: SubmissionId, val metadata: Map, - val fastaIds: List? = null, + val fastaIds: Set? = null, ) private fun invalidTsvFormatException(originalException: Exception) = UnprocessableEntityException( @@ -44,7 +45,12 @@ fun findAndValidateSubmissionIdHeader(headerNames: List): String { return submissionIdHeaders.first() } -fun extractFastaIdsFromRecord(record: CSVRecord, submissionId: String, recordNumber: Int): List { +fun extractAndValidateFastaIds( + record: CSVRecord, + submissionId: String, + recordNumber: Int, + maxSequencesPerEntry: Int? = null, +): Set { val headerNames = record.parser.headerNames return when (headerNames.contains(FASTA_IDS_HEADER)) { true -> { @@ -55,21 +61,83 @@ fun extractFastaIdsFromRecord(record: CSVRecord, submissionId: String, recordNum ) } - fastaIdValues.split(Regex(FASTA_IDS_SEPARATOR)) + val fastaIds = fastaIdValues.split(Regex(FASTA_IDS_SEPARATOR)) .map { it.trim() } .filter { it.isNotEmpty() } + + val duplicateFastaIds = fastaIds.groupingBy { it }.eachCount().filter { it.value > 1 }.keys + if (duplicateFastaIds.isNotEmpty()) { + throw UnprocessableEntityException( + "In metadata file: record #$recordNumber with id '$submissionId': " + + "found duplicate fasta ids in column '$FASTA_IDS_HEADER': " + + duplicateFastaIds.joinToString(", "), + ) + } + + if (maxSequencesPerEntry != null && fastaIds.size > maxSequencesPerEntry) { + throw UnprocessableEntityException( + "In metadata file: record #$recordNumber with id '$submissionId': " + + "found ${fastaIds.size} fasta ids but the maximum allowed number of " + + "sequences per entry is $maxSequencesPerEntry", + ) + } + + fastaIds.toSet() + } + false -> { + setOf(submissionId) } - false -> listOf(submissionId) } } -fun metadataEntryStreamAsSequence(metadataInputStream: InputStream): Sequence { +private fun setUpCsvParser(metadataInputStream: InputStream): CSVParser { val csvParser = try { CSVFormat.TDF.builder().setHeader().setSkipHeaderRecord(true).get() .parse(InputStreamReader(metadataInputStream)) } catch (e: CSVException) { throw invalidTsvFormatException(e) } + return csvParser +} + +private fun getValueAndValidateNoWhitespace(record: CSVRecord, fieldName: String, recordNumber: Int): String { + val fieldValue = record[fieldName] + if (fieldValue.isNullOrEmpty()) { + val rowValues = record.toList().joinToString("', '", prefix = "['", postfix = "']") + throw UnprocessableEntityException( + "Record #$recordNumber in the metadata file contains no value for '$fieldName'. Row: $rowValues", + ) + } + if (fieldValue.any { it.isWhitespace() }) { + throw UnprocessableEntityException( + "Record #$recordNumber in the metadata file: the value for '$fieldName' contains whitespace: '$fieldValue'", + ) + } + return fieldValue +} + +private fun validateMetadataNotEmpty(metadata: Map, submissionId: String, recordNumber: Int) { + if (metadata.isEmpty()) { + throw UnprocessableEntityException( + "In metadata file: record #$recordNumber with id $submissionId contains no metadata. This is invalid.", + ) + } +} + +private fun throwWithCsvExceptionUnwrapped(e: Exception): Nothing { + // CSVException is wrapped in UncheckedIOException during iteration + val cause = e.cause + if (cause is CSVException) { + throw invalidTsvFormatException(cause) + } + throw e +} + +fun metadataEntryStreamAsSequence( + metadataInputStream: InputStream, + maxSequencesPerEntry: Int? = null, +): Sequence { + val csvParser = setUpCsvParser(metadataInputStream) val headerNames = csvParser.headerNames val submissionIdHeader = findAndValidateSubmissionIdHeader(headerNames) @@ -78,47 +146,22 @@ fun metadataEntryStreamAsSequence(metadataInputStream: InputStream): Sequence val recordNumber = index + 1 // First data record is #1 (header not counted) - val submissionId = record[submissionIdHeader] - if (submissionId.isNullOrEmpty()) { - val rowValues = record.toList().joinToString("', '", prefix = "['", postfix = "']") - throw UnprocessableEntityException( - "Record #$recordNumber in the metadata file contains no value for '$submissionIdHeader'. Row: $rowValues", - ) - } - if (submissionId.any { it.isWhitespace() }) { - throw UnprocessableEntityException( - "Record #$recordNumber in the metadata file: the value for '$submissionIdHeader' contains whitespace: '$submissionId'", - ) - } + val submissionId = getValueAndValidateNoWhitespace(record, submissionIdHeader, recordNumber) - val fastaIds = extractFastaIdsFromRecord( - record, - submissionId, - recordNumber, - ) + val fastaIds = extractAndValidateFastaIds(record, submissionId, recordNumber, maxSequencesPerEntry) val metadata = record.toMap().filterKeys { it != submissionIdHeader && it != FASTA_IDS_HEADER } - val entry = MetadataEntry(submissionId, metadata, fastaIds) - if (entry.metadata.isEmpty()) { - throw UnprocessableEntityException( - "In metadata file: record #$recordNumber contains no metadata. This is invalid. Full record: $entry", - ) - } + validateMetadataNotEmpty(metadata, submissionId, recordNumber) - yield(entry) + yield(MetadataEntry(submissionId, metadata, fastaIds)) } } catch (e: java.io.UncheckedIOException) { - // CSVException is wrapped in UncheckedIOException during iteration - val cause = e.cause - if (cause is CSVException) { - throw invalidTsvFormatException(cause) - } - throw e + throwWithCsvExceptionUnwrapped(e) } } } @@ -127,16 +170,14 @@ data class RevisionEntry( val submissionId: SubmissionId, val accession: Accession, val metadata: Map, - val fastaIds: List? = null, + val fastaIds: Set? = null, ) -fun revisionEntryStreamAsSequence(metadataInputStream: InputStream): Sequence { - val csvParser = try { - CSVFormat.TDF.builder().setHeader().setSkipHeaderRecord(true).get() - .parse(InputStreamReader(metadataInputStream)) - } catch (e: CSVException) { - throw invalidTsvFormatException(e) - } +fun revisionEntryStreamAsSequence( + metadataInputStream: InputStream, + maxSequencesPerEntry: Int? = null, +): Sequence { + val csvParser = setUpCsvParser(metadataInputStream) val headerNames = csvParser.headerNames val submissionIdHeader = findAndValidateSubmissionIdHeader(headerNames) @@ -151,49 +192,22 @@ fun revisionEntryStreamAsSequence(metadataInputStream: InputStream): Sequence val recordNumber = index + 1 // First data record is #1 (header not counted) - val submissionId = record[submissionIdHeader] - if (submissionId.isNullOrEmpty()) { - val rowValues = record.toList().joinToString("', '", prefix = "['", postfix = "']") - throw UnprocessableEntityException( - "Record #$recordNumber in the metadata file contains no value for '$submissionIdHeader'. Row: $rowValues", - ) - } - val accession = record[ACCESSION_HEADER] - if (accession.isNullOrEmpty()) { - val rowValues = record.toList().joinToString("', '", prefix = "['", postfix = "']") - throw UnprocessableEntityException( - "Record #$recordNumber in the metadata file contains no value for '$ACCESSION_HEADER'. Row: $rowValues", - ) - } + val submissionId = getValueAndValidateNoWhitespace(record, submissionIdHeader, recordNumber) + val accession = getValueAndValidateNoWhitespace(record, ACCESSION_HEADER, recordNumber) - val fastaIds = extractFastaIdsFromRecord( - record, - submissionId, - recordNumber, - ) + val fastaIds = extractAndValidateFastaIds(record, submissionId, recordNumber, maxSequencesPerEntry) val metadata = record.toMap().filterKeys { it != submissionIdHeader && it != ACCESSION_HEADER && it != FASTA_IDS_HEADER } - val entry = RevisionEntry(submissionId, accession, metadata, fastaIds) + validateMetadataNotEmpty(metadata, submissionId, recordNumber) - if (entry.metadata.isEmpty()) { - throw UnprocessableEntityException( - "Record #$recordNumber in the metadata file contains no metadata columns: $entry", - ) - } - - yield(entry) + yield(RevisionEntry(submissionId, accession, metadata, fastaIds)) } } catch (e: java.io.UncheckedIOException) { - // CSVException is wrapped in UncheckedIOException during iteration - val cause = e.cause - if (cause is CSVException) { - throw invalidTsvFormatException(cause) - } - throw e + throwWithCsvExceptionUnwrapped(e) } } } diff --git a/backend/src/test/kotlin/org/loculus/backend/utils/MetadataEntryTest.kt b/backend/src/test/kotlin/org/loculus/backend/utils/MetadataEntryTest.kt index 908261ff69..4ffb9f7cc0 100644 --- a/backend/src/test/kotlin/org/loculus/backend/utils/MetadataEntryTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/utils/MetadataEntryTest.kt @@ -1,5 +1,9 @@ package org.loculus.backend.utils +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.containsString +import org.hamcrest.Matchers.equalTo +import org.hamcrest.Matchers.hasSize import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows import org.loculus.backend.controller.UnprocessableEntityException @@ -14,9 +18,9 @@ class MetadataEntryTest { """.trimIndent() val inputStream = ByteArrayInputStream(str.toByteArray()) val entries = metadataEntryStreamAsSequence(inputStream).toList() - assert(entries.size == 1) - assert(entries[0].submissionId == "foo") - assert(entries[0].metadata["Country"] == "bar") + assertThat(entries, hasSize(1)) + assertThat(entries[0].submissionId, equalTo("foo")) + assertThat(entries[0].metadata["Country"], equalTo("bar")) } @Test @@ -39,8 +43,8 @@ class MetadataEntryTest { val exception = assertThrows { metadataEntryStreamAsSequence(inputStream).toList() } - assert(exception.message!!.contains("whitespace")) - assert(exception.message!!.contains("Record #1")) // First data record is #1 + assertThat(exception.message, containsString("whitespace")) + assertThat(exception.message, containsString("Record #1")) // First data record is #1 } @Test @@ -53,8 +57,8 @@ class MetadataEntryTest { val exception = assertThrows { metadataEntryStreamAsSequence(inputStream).toList() } - assert(exception.message!!.contains("Record #1")) - assert(exception.message!!.contains("contains no value for")) + assertThat(exception.message, containsString("Record #1")) + assertThat(exception.message, containsString("contains no value for")) } @Test @@ -70,7 +74,7 @@ class MetadataEntryTest { metadataEntryStreamAsSequence(inputStream).toList() } // The error should occur on record #3 (foo1=1, foo2=2, empty=3) - assert(exception.message!!.contains("Record #3")) + assertThat(exception.message, containsString("Record #3")) } @Test @@ -84,8 +88,8 @@ class MetadataEntryTest { val exception = assertThrows { metadataEntryStreamAsSequence(inputStream).toList() } - assert(exception.message!!.contains("not a valid TSV file")) - assert(exception.message!!.contains("Common causes include")) + assertThat(exception.message, containsString("not a valid TSV file")) + assertThat(exception.message, containsString("Common causes include")) } @Test @@ -100,7 +104,7 @@ class MetadataEntryTest { val exception = assertThrows { metadataEntryStreamAsSequence(inputStream).toList() } - assert(exception.message!!.contains("whitespace")) + assertThat(exception.message, containsString("whitespace")) } @Test @@ -120,8 +124,138 @@ class MetadataEntryTest { val exception = assertThrows { metadataEntryStreamAsSequence(inputStream).toList() } - assert(exception.message!!.contains("not a valid TSV file")) - assert(exception.message!!.contains("Common causes include")) + assertThat(exception.message, containsString("not a valid TSV file")) + assertThat(exception.message, containsString("Common causes include")) + } + + @Test + fun `test maxSequencesPerEntry not set allows multiple sequences`() { + val str = """ + submissionId${'\t'}fastaIds${'\t'}Country + foo${'\t'}seq1 seq2 seq3${'\t'}bar + """.trimIndent() + val inputStream = ByteArrayInputStream(str.toByteArray()) + val entries = metadataEntryStreamAsSequence(inputStream, maxSequencesPerEntry = null).toList() + assertThat(entries, hasSize(1)) + assertThat(entries[0].submissionId, equalTo("foo")) + assertThat(entries[0].fastaIds, equalTo(setOf("seq1", "seq2", "seq3"))) + } + + @Test + fun `test maxSequencesPerEntry allows sequences within limit`() { + val str = """ + submissionId${'\t'}fastaIds${'\t'}Country + foo${'\t'}seq1 seq2${'\t'}bar + """.trimIndent() + val inputStream = ByteArrayInputStream(str.toByteArray()) + val entries = metadataEntryStreamAsSequence(inputStream, maxSequencesPerEntry = 3).toList() + assertThat(entries, hasSize(1)) + assertThat(entries[0].submissionId, equalTo("foo")) + assertThat(entries[0].fastaIds, equalTo(setOf("seq1", "seq2"))) + } + + @Test + fun `test maxSequencesPerEntry allows sequences at exact limit`() { + val str = """ + submissionId${'\t'}fastaIds${'\t'}Country + foo${'\t'}seq1 seq2 seq3${'\t'}bar + """.trimIndent() + val inputStream = ByteArrayInputStream(str.toByteArray()) + val entries = metadataEntryStreamAsSequence(inputStream, maxSequencesPerEntry = 3).toList() + assertThat(entries, hasSize(1)) + assertThat(entries[0].submissionId, equalTo("foo")) + assertThat(entries[0].fastaIds, equalTo(setOf("seq1", "seq2", "seq3"))) + } + + @Test + fun `test maxSequencesPerEntry rejects sequences exceeding limit`() { + val str = """ + submissionId${'\t'}fastaIds${'\t'}Country + foo${'\t'}seq1 seq2 seq3 seq4${'\t'}bar + """.trimIndent() + val inputStream = ByteArrayInputStream(str.toByteArray()) + val exception = assertThrows { + metadataEntryStreamAsSequence(inputStream, maxSequencesPerEntry = 3).toList() + } + assertThat(exception.message, containsString("record #1")) + assertThat(exception.message, containsString("foo")) + assertThat(exception.message, containsString("found 4 fasta ids")) + assertThat(exception.message, containsString("maximum allowed number of sequences per entry is 3")) + } + + @Test + fun `test maxSequencesPerEntry with single sequence limit`() { + val str = """ + submissionId${'\t'}fastaIds${'\t'}Country + foo${'\t'}seq1 seq2${'\t'}bar + """.trimIndent() + val inputStream = ByteArrayInputStream(str.toByteArray()) + val exception = assertThrows { + metadataEntryStreamAsSequence(inputStream, maxSequencesPerEntry = 1).toList() + } + assertThat(exception.message, containsString("record #1")) + assertThat(exception.message, containsString("foo")) + assertThat(exception.message, containsString("found 2 fasta ids")) + assertThat(exception.message, containsString("maximum allowed number of sequences per entry is 1")) + } + + @Test + fun `test maxSequencesPerEntry allows single sequence when limit is 1`() { + val str = """ + submissionId${'\t'}fastaIds${'\t'}Country + foo${'\t'}seq1${'\t'}bar + """.trimIndent() + val inputStream = ByteArrayInputStream(str.toByteArray()) + val entries = metadataEntryStreamAsSequence(inputStream, maxSequencesPerEntry = 1).toList() + assertThat(entries, hasSize(1)) + assertThat(entries[0].submissionId, equalTo("foo")) + assertThat(entries[0].fastaIds, equalTo(setOf("seq1"))) + } + + @Test + fun `test maxSequencesPerEntry correct record number for multiple rows`() { + val str = """ + submissionId${'\t'}fastaIds${'\t'}Country + foo1${'\t'}seq1${'\t'}bar + foo2${'\t'}seq2 seq3${'\t'}bar + """.trimIndent() + val inputStream = ByteArrayInputStream(str.toByteArray()) + val exception = assertThrows { + metadataEntryStreamAsSequence(inputStream, maxSequencesPerEntry = 1).toList() + } + assertThat(exception.message, containsString("record #2")) + assertThat(exception.message, containsString("foo2")) + } + + @Test + fun `test multiple duplicate fasta IDs are all reported`() { + val str = """ + submissionId${'\t'}fastaIds${'\t'}Country + foo${'\t'}seq1 seq2 seq1 seq2 seq3${'\t'}bar + """.trimIndent() + val inputStream = ByteArrayInputStream(str.toByteArray()) + val exception = assertThrows { + metadataEntryStreamAsSequence(inputStream).toList() + } + assertThat(exception.message, containsString("record #1")) + assertThat(exception.message, containsString("foo")) + assertThat(exception.message, containsString("duplicate fasta ids")) + assertThat(exception.message, containsString("seq1")) + assertThat(exception.message, containsString("seq2")) + } + + @Test + fun `test duplicate detection works with maxSequencesPerEntry`() { + val str = """ + submissionId${'\t'}fastaIds${'\t'}Country + foo${'\t'}seq1 seq1${'\t'}bar + """.trimIndent() + val inputStream = ByteArrayInputStream(str.toByteArray()) + val exception = assertThrows { + metadataEntryStreamAsSequence(inputStream, maxSequencesPerEntry = 3).toList() + } + assertThat(exception.message, containsString("duplicate fasta ids")) + assertThat(exception.message, containsString("seq1")) } } @@ -134,10 +268,10 @@ class RevisionEntryTest { """.trimIndent() val inputStream = ByteArrayInputStream(str.toByteArray()) val entries = revisionEntryStreamAsSequence(inputStream).toList() - assert(entries.size == 1) - assert(entries[0].submissionId == "foo") - assert(entries[0].accession == "ACC123") - assert(entries[0].metadata["Country"] == "bar") + assertThat(entries, hasSize(1)) + assertThat(entries[0].submissionId, equalTo("foo")) + assertThat(entries[0].accession, equalTo("ACC123")) + assertThat(entries[0].metadata["Country"], equalTo("bar")) } @Test @@ -161,8 +295,8 @@ class RevisionEntryTest { val exception = assertThrows { revisionEntryStreamAsSequence(inputStream).toList() } - assert(exception.message!!.contains("not a valid TSV file")) - assert(exception.message!!.contains("Common causes include")) + assertThat(exception.message, containsString("not a valid TSV file")) + assertThat(exception.message, containsString("Common causes include")) } @Test @@ -182,8 +316,8 @@ class RevisionEntryTest { val exception = assertThrows { revisionEntryStreamAsSequence(inputStream).toList() } - assert(exception.message!!.contains("not a valid TSV file")) - assert(exception.message!!.contains("Common causes include")) + assertThat(exception.message, containsString("not a valid TSV file")) + assertThat(exception.message, containsString("Common causes include")) } @Test @@ -196,8 +330,8 @@ class RevisionEntryTest { val exception = assertThrows { revisionEntryStreamAsSequence(inputStream).toList() } - assert(exception.message!!.contains("Record #1")) - assert(exception.message!!.contains("contains no value for")) + assertThat(exception.message, containsString("Record #1")) + assertThat(exception.message, containsString("contains no value for")) } @Test @@ -210,7 +344,79 @@ class RevisionEntryTest { val exception = assertThrows { revisionEntryStreamAsSequence(inputStream).toList() } - assert(exception.message!!.contains("Record #1")) - assert(exception.message!!.contains("accession")) + assertThat(exception.message, containsString("Record #1")) + assertThat(exception.message, containsString("accession")) + } + + @Test + fun `test revision maxSequencesPerEntry allows sequences within limit`() { + val str = """ + submissionId${'\t'}accession${'\t'}fastaIds${'\t'}Country + foo${'\t'}ACC123${'\t'}seq1 seq2${'\t'}bar + """.trimIndent() + val inputStream = ByteArrayInputStream(str.toByteArray()) + val entries = revisionEntryStreamAsSequence(inputStream, maxSequencesPerEntry = 3).toList() + assertThat(entries, hasSize(1)) + assertThat(entries[0].submissionId, equalTo("foo")) + assertThat(entries[0].fastaIds, equalTo(setOf("seq1", "seq2"))) + } + + @Test + fun `test revision maxSequencesPerEntry rejects sequences exceeding limit`() { + val str = """ + submissionId${'\t'}accession${'\t'}fastaIds${'\t'}Country + foo${'\t'}ACC123${'\t'}seq1 seq2 seq3${'\t'}bar + """.trimIndent() + val inputStream = ByteArrayInputStream(str.toByteArray()) + val exception = assertThrows { + revisionEntryStreamAsSequence(inputStream, maxSequencesPerEntry = 2).toList() + } + assertThat(exception.message, containsString("record #1")) + assertThat(exception.message, containsString("foo")) + assertThat(exception.message, containsString("found 3 fasta ids")) + assertThat(exception.message, containsString("maximum allowed number of sequences per entry is 2")) + } + + @Test + fun `test revision maxSequencesPerEntry with single sequence limit`() { + val str = """ + submissionId${'\t'}accession${'\t'}fastaIds${'\t'}Country + foo${'\t'}ACC123${'\t'}seq1${'\t'}bar + """.trimIndent() + val inputStream = ByteArrayInputStream(str.toByteArray()) + val entries = revisionEntryStreamAsSequence(inputStream, maxSequencesPerEntry = 1).toList() + assertThat(entries, hasSize(1)) + assertThat(entries[0].submissionId, equalTo("foo")) + assertThat(entries[0].fastaIds, equalTo(setOf("seq1"))) + } + + @Test + fun `test revision duplicate fasta IDs are rejected`() { + val str = """ + submissionId${'\t'}accession${'\t'}fastaIds${'\t'}Country + foo${'\t'}ACC123${'\t'}seq1 seq2 seq1${'\t'}bar + """.trimIndent() + val inputStream = ByteArrayInputStream(str.toByteArray()) + val exception = assertThrows { + revisionEntryStreamAsSequence(inputStream).toList() + } + assertThat(exception.message, containsString("record #1")) + assertThat(exception.message, containsString("foo")) + assertThat(exception.message, containsString("duplicate fasta ids")) + assertThat(exception.message, containsString("seq1")) + } + + @Test + fun `test revision duplicate detection works with maxSequencesPerEntry`() { + val str = """ + submissionId${'\t'}accession${'\t'}fastaIds${'\t'}Country + foo${'\t'}ACC123${'\t'}seq1 seq1${'\t'}bar + """.trimIndent() + val inputStream = ByteArrayInputStream(str.toByteArray()) + val exception = assertThrows { + revisionEntryStreamAsSequence(inputStream, maxSequencesPerEntry = 3).toList() + } + assertThat(exception.message, containsString("duplicate fasta ids")) + assertThat(exception.message, containsString("seq1")) } } diff --git a/kubernetes/loculus/templates/_submission-data-types.tpl b/kubernetes/loculus/templates/_submission-data-types.tpl index e5b9dea1c7..a2e9592e49 100644 --- a/kubernetes/loculus/templates/_submission-data-types.tpl +++ b/kubernetes/loculus/templates/_submission-data-types.tpl @@ -3,15 +3,14 @@ submissionDataTypes: {{- if (hasKey . "submissionDataTypes") }} {{- with .submissionDataTypes }} consensusSequences: {{ (hasKey . "consensusSequences") | ternary .consensusSequences "true" }} + {{- if (hasKey . "maxSequencesPerEntry") }} + maxSequencesPerEntry: {{ .maxSequencesPerEntry }} {{- end }} - {{- else }} - consensusSequences: true - {{- end}} - {{- if (hasKey . "submissionDataTypes") }} - {{- with .submissionDataTypes }} {{- if (hasKey . "files") }} files: {{ .files | toJson }} {{- end }} {{- end }} + {{- else }} + consensusSequences: true {{- end }} {{- end }} diff --git a/kubernetes/loculus/values.schema.json b/kubernetes/loculus/values.schema.json index 0f22052e10..a143200ec9 100644 --- a/kubernetes/loculus/values.schema.json +++ b/kubernetes/loculus/values.schema.json @@ -337,6 +337,12 @@ "default": true, "description": "If false, the submission form will not allow submission of consensus sequences (i.e. the sequences file must be omitted). All consensus sequence related parts on the website will be hidden." }, + "maxSequencesPerEntry": { + "groups": ["schema"], + "type": ["integer", "null"], + "default": null, + "description": "Maximum number of sequences allowed per submission entry. Set to null for unlimited sequences per entry. For single-segment organisms, set to 1 to ensure only one sequence per entry." + }, "files": { "type": "object", "properties": { diff --git a/kubernetes/loculus/values.yaml b/kubernetes/loculus/values.yaml index 56cf23e28f..aa08b73b72 100644 --- a/kubernetes/loculus/values.yaml +++ b/kubernetes/loculus/values.yaml @@ -56,6 +56,9 @@ lineageSystemDefinitions: 1: https://pathoplexus.github.io/silo-lineage-hierarchy-definitions/definitions/cchf/S/2025-09-24--07-29-03Z/lineages.yaml defaultOrganismConfig: &defaultOrganismConfig schema: &schema + submissionDataTypes: &defaultSubmissionDataTypes + consensusSequences: true + maxSequencesPerEntry: 1 loadSequencesAutomatically: true earliestReleaseDate: enabled: true @@ -1469,6 +1472,7 @@ defaultOrganisms: sequence: "[[URL:https://corneliusroemer.github.io/seqs/artefacts/west-nile/prM.fasta]]" dummy-organism: schema: + submissionDataTypes: *defaultSubmissionDataTypes image: "https://www.un.org/sites/un2.un.org/files/field/image/1583952355.1997.jpg" organismName: "Test Dummy Organism" metadataTemplate: @@ -1612,6 +1616,7 @@ defaultOrganisms: not-aligned-organism: enabled: true schema: + submissionDataTypes: *defaultSubmissionDataTypes image: "https://cdn.who.int/media/images/default-source/mca/mca-covid-19/coronavirus-2.tmb-1920v.jpg?sfvrsn=4dba955c_19" organismName: "Test organism (without alignment)" metadata: @@ -1694,6 +1699,9 @@ defaultOrganisms: <<: *defaultOrganismConfig schema: <<: *schema + submissionDataTypes: + <<: *defaultSubmissionDataTypes + maxSequencesPerEntry: 3 organismName: "Crimean-Congo Hemorrhagic Fever Virus" nucleotideSequences: [L, M, S] image: "/images/organisms/cchf_small.jpg" From 9889a833249ee66404d07c5cc44af33368f45f5f Mon Sep 17 00:00:00 2001 From: "Anna (Anya) Parker" <50943381+anna-parker@users.noreply.github.com> Date: Tue, 2 Dec 2025 15:11:34 +0100 Subject: [PATCH 34/44] feat(config): update values.schema.json for multi path (#5582) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolves https://github.com/loculus-project/loculus/issues/5571 Adds the new config structure to the kubernetes/loculus/values.schema.json. I also renamed `nucleotideSequences` to `nextcladeSequenceAndDatasets` to avoid confusion as preprocessing expects different items in this list of dictionaries. ### Screenshot image ### PR Checklist - [ ] All necessary documentation has been adapted. - [ ] The implemented feature is covered by appropriate, automated tests. - [ ] Any manual testing that has been done is documented (i.e. what exactly was tested?) 🚀 Preview: Add `preview` label to enable --- .../for-administrators/my-first-loculus.md | 2 +- .../setup-with-k3d-and-nginx.mdx | 2 +- .../setup-with-kubernetes.md | 2 +- .../docs/reference/helm-chart-config.mdx | 4 + kubernetes/loculus/values.schema.json | 75 ++++++++++++++++--- kubernetes/loculus/values.yaml | 13 ++-- preprocessing/nextclade/README.md | 4 +- .../src/loculus_preprocessing/config.py | 10 +-- .../src/loculus_preprocessing/nextclade.py | 16 ++-- .../src/loculus_preprocessing/prepro.py | 6 +- .../tests/multi_pathogen_config.yaml | 2 +- .../nextclade/tests/multi_segment_config.yaml | 2 +- .../tests/multi_segment_config_unaligned.yaml | 2 +- .../tests/single_segment_config.yaml | 2 +- .../test_metadata_processing_functions.py | 2 +- 15 files changed, 101 insertions(+), 43 deletions(-) diff --git a/docs/src/content/docs/for-administrators/my-first-loculus.md b/docs/src/content/docs/for-administrators/my-first-loculus.md index 30524dfd7a..c485868c8f 100644 --- a/docs/src/content/docs/for-administrators/my-first-loculus.md +++ b/docs/src/content/docs/for-administrators/my-first-loculus.md @@ -153,7 +153,7 @@ organisms: configFile: log_level: DEBUG batch_size: 100 - nucleotideSequences: + nextclade_sequence_and_datasets: - name: 'main' genes: [] referenceGenomes: diff --git a/docs/src/content/docs/for-administrators/setup-with-k3d-and-nginx.mdx b/docs/src/content/docs/for-administrators/setup-with-k3d-and-nginx.mdx index 4f6f383c18..3403a81032 100644 --- a/docs/src/content/docs/for-administrators/setup-with-k3d-and-nginx.mdx +++ b/docs/src/content/docs/for-administrators/setup-with-k3d-and-nginx.mdx @@ -170,7 +170,7 @@ organisms: configFile: log_level: DEBUG batch_size: 100 - nucleotideSequences: + nextclade_sequence_and_datasets: - name: 'main' genes: [] referenceGenomes: diff --git a/docs/src/content/docs/for-administrators/setup-with-kubernetes.md b/docs/src/content/docs/for-administrators/setup-with-kubernetes.md index d928559589..7b733da9fe 100644 --- a/docs/src/content/docs/for-administrators/setup-with-kubernetes.md +++ b/docs/src/content/docs/for-administrators/setup-with-kubernetes.md @@ -144,7 +144,7 @@ preprocessing: - 'prepro' configFile: log_level: DEBUG - nucleotideSequences: + nextclade_sequence_and_datasets: - name: 'main' nextclade_dataset_name: nextstrain/ebola/zaire genes: [NP, VP35, VP40, GP, sGP, ssGP, VP30, VP24, L] diff --git a/docs/src/content/docs/reference/helm-chart-config.mdx b/docs/src/content/docs/reference/helm-chart-config.mdx index 825263b5bc..45cf0cb076 100644 --- a/docs/src/content/docs/reference/helm-chart-config.mdx +++ b/docs/src/content/docs/reference/helm-chart-config.mdx @@ -134,6 +134,10 @@ The values for `args` and `configFile` depend on the used preprocessing pipeline +##### Nextclade Preprocessing Pipeline Sequence and Dataset Objects (type) + + + For more details on the Nextclade preprocessing pipeline, please see [here](../../for-administrators/existing-preprocessing-pipelines/#nextclade-based-pipeline). diff --git a/kubernetes/loculus/values.schema.json b/kubernetes/loculus/values.schema.json index a143200ec9..56cc505de9 100644 --- a/kubernetes/loculus/values.schema.json +++ b/kubernetes/loculus/values.schema.json @@ -593,16 +593,72 @@ "enum": ["ALL", "ANY"], "description": "If multi-segmented viruses should require ALL segments (that the user provides) align or ANY segment aligns" }, - "nextclade_dataset_server": { + "segment_classification_method": { "groups": ["nextcladePipelineConfigFile"], "docsIncludePrefix": false, - "type": "string" + "type": "string", + "enum": ["minimizer", "align"], + "description": "Method to classify segments for multi-segmented viruses" }, - "nextclade_dataset_name": { + "nextclade_dataset_server": { "groups": ["nextcladePipelineConfigFile"], "docsIncludePrefix": false, "type": "string", - "description": "Required if sequences should be aligned" + "description": "Server from which to download nextclade datasets." + }, + "nextclade_sequence_and_datasets": { + "groups": ["nextcladePipelineConfigFile"], + "docsIncludePrefix": false, + "type": "array", + "description": "Array of [nextcladeSequenceAndDataset](#nextclade-preprocessing-pipeline-sequence-and-dataset-objects-type) objects containing nucleotide sequence names and datasets.", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "groups": ["nextcladeSequenceAndDataset"], + "docsIncludePrefix": false, + "type": "string", + "description": "Name of the nucleotide sequence to which this dataset applies - defaults to 'main'." + }, + "nextclade_dataset_name": { + "groups": ["nextcladeSequenceAndDataset"], + "docsIncludePrefix": false, + "type": "string", + "description": "Name of the nextclade dataset to use to align the sequence - required if this sequence should be aligned." + }, + "nextclade_dataset_tag": { + "groups": ["nextcladeSequenceAndDataset"], + "docsIncludePrefix": false, + "type": "string", + "description": "Optional. Tag of the nextclade dataset to use to align the sequence." + }, + "nextclade_dataset_server": { + "groups": ["nextcladeSequenceAndDataset"], + "docsIncludePrefix": false, + "type": "string", + "description": "Optional. Server from which to download the nextclade dataset to use to align the sequence. If not specified, use the global nextclade_dataset_server." + }, + "accepted_sort_matches": { + "groups": ["nextcladeSequenceAndDataset"], + "docsIncludePrefix": false, + "type": "array", + "description": "Optional. Array of strings. The dataset names that are accepted as matches when using nextclade sort, if not specified use only accept matches with the same name or nextclade_dataset_name." + }, + "genes": { + "groups": ["nextcladeSequenceAndDataset"], + "docsIncludePrefix": false, + "type": "array", + "description": "Array of strings. The names of the genes to be extracted from the sequence alignment results (should correspond to gene names in the nextclade dataset)." + }, + "gene_prefix": { + "groups": ["nextcladeSequenceAndDataset"], + "docsIncludePrefix": false, + "type": "string", + "description": "Optional. Prefix to add to extracted gene names when sending the processing results to the backend. For example, if the nucleotide sequence is for segment (or subtype) A of a multi-segmented virus, then setting gene_prefix to 'A_' will result in gene names 'A_gene1', 'A_gene2', etc." + } + } + } }, "require_nextclade_sort_match": { "groups": ["nextcladePipelineConfigFile"], @@ -614,13 +670,7 @@ "groups": ["nextcladePipelineConfigFile"], "docsIncludePrefix": false, "type": "string", - "description": "Minimizer used for nextclade sort (if require_nextclade_sort_match is true), if not specified use default nextclade server minimizer" - }, - "accepted_dataset_matches": { - "groups": ["nextcladePipelineConfigFile"], - "docsIncludePrefix": false, - "type": "array", - "description": "Array of strings. The dataset names that are accepted as matches when using nextclade sort, if not specified use nextclade_dataset_name." + "description": "Minimizer used for nextclade sort (if require_nextclade_sort_match or if segments are to be assigned using nextclade sort)" }, "backend_request_timeout_seconds": { "groups": ["nextcladePipelineConfigFile"], @@ -628,7 +678,8 @@ "type": "integer", "description": "Timeout in seconds for requests to the backend server for data to process" } - } + }, + "required": ["nextclade_sequence_and_datasets"] } }, "required": ["version", "image"] diff --git a/kubernetes/loculus/values.yaml b/kubernetes/loculus/values.yaml index aa08b73b72..51c4cbb57e 100644 --- a/kubernetes/loculus/values.yaml +++ b/kubernetes/loculus/values.yaml @@ -1329,7 +1329,7 @@ defaultOrganismConfig: &defaultOrganismConfig log_level: DEBUG batch_size: 100 create_embl_file: true - nucleotideSequences: + nextclade_sequence_and_datasets: - name: "main" genes: [] nextclade_dataset_server: https://data.clades.nextstrain.org/v3 @@ -1351,7 +1351,7 @@ defaultOrganisms: version: [1, 2] configFile: <<: *preprocessingConfigFile - nucleotideSequences: + nextclade_sequence_and_datasets: - nextclade_dataset_name: nextstrain/ebola/sudan genes: [NP, VP35, VP40, GP, sGP, ssGP, VP30, VP24, L] nextclade_dataset_server: https://raw.githubusercontent.com/nextstrain/nextclade_data/ebola/data_output @@ -1428,7 +1428,7 @@ defaultOrganisms: version: 1 configFile: <<: *preprocessingConfigFile - nucleotideSequences: + nextclade_sequence_and_datasets: - nextclade_dataset_name: nextstrain/wnv/all-lineages genes: [capsid, prM, env, NS1, NS2A, NS2B, NS3, NS4A, 2K, NS4B, NS5] nextclade_dataset_server: https://raw.githubusercontent.com/nextstrain/nextclade_data/wnv/data_output @@ -1689,6 +1689,9 @@ defaultOrganisms: configFile: log_level: DEBUG batch_size: 100 + nextclade_sequence_and_datasets: + - name: "main" + genes: [] referenceGenomes: singleReference: nucleotideSequences: @@ -1754,7 +1757,7 @@ defaultOrganisms: <<: *preprocessingConfigFile log_level: DEBUG minimizer_index: https://loculus-project.github.io/nextclade-sort-minimizers/data/CCHF/minimizer.json - nucleotideSequences: + nextclade_sequence_and_datasets: - name: L nextclade_dataset_name: community/pathoplexus/cchfv/L genes: [RdRp] @@ -1880,7 +1883,7 @@ defaultOrganisms: <<: *preprocessingConfigFile segment_classification_method: "minimizer" minimizer_index: "https://raw.githubusercontent.com/alejandra-gonzalezsanchez/loculus-evs/master/evs_minimizer-index.json" - nucleotideSequences: + nextclade_sequence_and_datasets: - name: CV-A16 nextclade_dataset_name: enpen/enterovirus/cv-a16 accepted_sort_matches: ["community/hodcroftlab/enterovirus/cva16", "community/hodcroftlab/enterovirus/enterovirus/linked/CV-A16"] diff --git a/preprocessing/nextclade/README.md b/preprocessing/nextclade/README.md index 1cdb61fb0f..b89f357deb 100644 --- a/preprocessing/nextclade/README.md +++ b/preprocessing/nextclade/README.md @@ -224,7 +224,7 @@ To add multiple preprocessing pipelines alter the preprocessing section of the ` version: 1 dockerTag: commit-xxxxx configFile: - nucleotideSequences: + nextclade_sequence_and_datasets: - name: main # default value, not actually required nextclade_dataset_name: nextstrain/wnv/all-lineages batch_size: 100 @@ -234,7 +234,7 @@ To add multiple preprocessing pipelines alter the preprocessing section of the ` version: 2 dockerTag: commit-yyyyyyy configFile: - nucleotideSequences: + nextclade_sequence_and_datasets: - name: main nextclade_dataset_name: nextstrain/wnv/all-lineages batch_size: 100 diff --git a/preprocessing/nextclade/src/loculus_preprocessing/config.py b/preprocessing/nextclade/src/loculus_preprocessing/config.py index 5f02009285..9b0cfe3422 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/config.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/config.py @@ -66,7 +66,7 @@ class Config: keycloak_token_path: str = "realms/loculus/protocol/openid-connect/token" # noqa: S105 organism: str = "mpox" - nucleotideSequences: list[NextcladeSequenceAndDataset] = dataclasses.field( # noqa: N815 + nextclade_sequence_and_datasets: list[NextcladeSequenceAndDataset] = dataclasses.field( default_factory=list ) processing_spec: dict[str, dict[str, Any]] = dataclasses.field(default_factory=dict) @@ -94,7 +94,7 @@ def assign_nextclade_sequence_and_dataset( nuc_seq_values: list[dict[str, Any]], config: Config ) -> list[NextcladeSequenceAndDataset]: if not isinstance(nuc_seq_values, list): - error_msg = f"nucleotideSequences should be a list of dicts, got: {type(nuc_seq_values)}" + error_msg = f"nextclade_sequence_and_datasets should be a list of dicts, got: {type(nuc_seq_values)}" logger.error(error_msg) raise ValueError(error_msg) nextclade_sequence_and_dataset_list: list[NextcladeSequenceAndDataset] = [] @@ -113,7 +113,7 @@ def assign_nextclade_sequence_and_dataset( def set_alignment_requirement(config: Config) -> AlignmentRequirement: need_nextclade_dataset: bool = False - for sequence in config.nucleotideSequences: + for sequence in config.nextclade_sequence_and_datasets: if sequence.nextclade_dataset_name: need_nextclade_dataset = True if not need_nextclade_dataset: @@ -128,7 +128,7 @@ def load_config_from_yaml(config_file: str, config: Config | None = None) -> Con logger.debug(f"Loaded config from {config_file}: {yaml_config}") for key, value in yaml_config.items(): if value is not None and hasattr(config, key): - if key == "nucleotideSequences": + if key == "nextclade_sequence_and_datasets": setattr(config, key, assign_nextclade_sequence_and_dataset(value, config)) continue attr = getattr(config, key) @@ -217,7 +217,7 @@ def get_config(config_file: str | None = None, ignore_args: bool = False) -> Con config.alignment_requirement = set_alignment_requirement(config) - if len(config.nucleotideSequences) > 1: + if len(config.nextclade_sequence_and_datasets) > 1: config.multi_segment = True return config diff --git a/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py b/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py index c6952b6952..47622a0d1d 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py @@ -324,7 +324,7 @@ def assign_segment( best_hit = best_hits[best_hits["seqName"] == seq_id] not_found = True - for segment in config.nucleotideSequences: + for segment in config.nextclade_sequence_and_datasets: if ( config.segment_classification_method == SegmentClassificationMethod.ALIGN and best_hit["segment"].iloc[0] == segment.name @@ -385,7 +385,7 @@ def assign_segment_with_nextclade_align( ) -> SegmentAssignmentBatch: """ Run nextclade align - - assert sequence aligns to one of the segments in config.nucleotideSequences + - assert sequence aligns to one of the segments in config.nextclade_sequence_and_datasets """ batch = SegmentAssignmentBatch() @@ -394,7 +394,7 @@ def assign_segment_with_nextclade_align( input_file = result_dir + "/input.fasta" id_map = write_nextclade_input_fasta(unprocessed, input_file) - for sequence_and_dataset in config.nucleotideSequences: + for sequence_and_dataset in config.nextclade_sequence_and_datasets: segment = sequence_and_dataset.name result_file_seg = f"{result_dir}/sort_output_{segment}.tsv" @@ -527,11 +527,11 @@ def assign_segment_using_header( ) -> SegmentAssignment: segment_assignment = SegmentAssignment() duplicate_segments = set() - if not config.nucleotideSequences: + if not config.nextclade_sequence_and_datasets: return segment_assignment if not config.multi_segment: return assign_single_segment(input_unaligned_sequences, config) - for sequence_and_dataset in config.nucleotideSequences: + for sequence_and_dataset in config.nextclade_sequence_and_datasets: segment = sequence_and_dataset.name unaligned_segment = [ data @@ -567,7 +567,7 @@ def assign_segment_using_header( f"{', '.join(remaining_segments)}. " "Each metadata entry can have multiple corresponding fasta sequence " "entries with format _ valid segments are: " - f"{', '.join([seq.name for seq in config.nucleotideSequences])}." + f"{', '.join([seq.name for seq in config.nextclade_sequence_and_datasets])}." ) ) if len(segment_assignment.unalignedNucleotideSequences) == 0 and not duplicate_segments: @@ -692,7 +692,7 @@ def enrich_with_nextclade( # noqa: PLR0914 AccessionVersion, defaultdict[GeneName, list[AminoAcidInsertion]] ] = defaultdict(lambda: defaultdict(list)) with TemporaryDirectory(delete=not config.keep_tmp_dir) as result_dir: - for sequence_and_dataset in config.nucleotideSequences: + for sequence_and_dataset in config.nextclade_sequence_and_datasets: segment = sequence_and_dataset.name result_dir_seg = result_dir + "/" + segment input_file = result_dir_seg + "/input.fasta" @@ -773,7 +773,7 @@ def enrich_with_nextclade( # noqa: PLR0914 def download_nextclade_dataset(dataset_dir: str, config: Config) -> None: - for sequence_and_dataset in config.nucleotideSequences: + for sequence_and_dataset in config.nextclade_sequence_and_datasets: dataset_download_command = [ "nextclade3", "dataset", diff --git a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py index 2065ab1a05..33db7990b0 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py @@ -324,7 +324,7 @@ def get_output_metadata( continue if output_field.startswith("length_") and output_field[7:] in [ - seq.name for seq in config.nucleotideSequences + seq.name for seq in config.nextclade_sequence_and_datasets ]: segment = output_field[7:] output_metadata[output_field] = get_sequence_length( @@ -397,7 +397,7 @@ def alignment_errors_warnings( ) return (errors, warnings) aligned_segments = set() - for sequence_and_dataset in config.nucleotideSequences: + for sequence_and_dataset in config.nextclade_sequence_and_datasets: segment = sequence_and_dataset.name if segment not in unprocessed.unalignedNucleotideSequences: continue @@ -440,7 +440,7 @@ def unpack_annotations(config, nextclade_metadata: dict[str, Any] | None) -> dic if not config.create_embl_file or not nextclade_metadata: return None annotations: dict[str, Any] = {} - for sequence_and_dataset in config.nucleotideSequences: + for sequence_and_dataset in config.nextclade_sequence_and_datasets: segment = sequence_and_dataset.name if segment in nextclade_metadata: annotations[segment] = None diff --git a/preprocessing/nextclade/tests/multi_pathogen_config.yaml b/preprocessing/nextclade/tests/multi_pathogen_config.yaml index af4a99aa43..e1ae34c8ec 100644 --- a/preprocessing/nextclade/tests/multi_pathogen_config.yaml +++ b/preprocessing/nextclade/tests/multi_pathogen_config.yaml @@ -4,7 +4,7 @@ alignment_requirement: ALL log_level: DEBUG minimizer_index: TEST nextclade_dataset_server: TEST -nucleotideSequences: +nextclade_sequence_and_datasets: - name: ebola-sudan nextclade_dataset_name: ebola-dataset/ebola-sudan accepted_sort_matches: ebola-sudan diff --git a/preprocessing/nextclade/tests/multi_segment_config.yaml b/preprocessing/nextclade/tests/multi_segment_config.yaml index f92c01a222..27b67f73f2 100644 --- a/preprocessing/nextclade/tests/multi_segment_config.yaml +++ b/preprocessing/nextclade/tests/multi_segment_config.yaml @@ -3,7 +3,7 @@ alignment_requirement: ALL log_level: DEBUG minimizer_index: TEST segment_classification_method: "minimizer" -nucleotideSequences: +nextclade_sequence_and_datasets: - name: ebola-sudan nextclade_dataset_name: ebola-dataset/ebola-sudan genes: [NPEbolaSudan, VP35EbolaSudan, LEbolaSudan] diff --git a/preprocessing/nextclade/tests/multi_segment_config_unaligned.yaml b/preprocessing/nextclade/tests/multi_segment_config_unaligned.yaml index bc82c15079..9d6fc1eae1 100644 --- a/preprocessing/nextclade/tests/multi_segment_config_unaligned.yaml +++ b/preprocessing/nextclade/tests/multi_segment_config_unaligned.yaml @@ -2,7 +2,7 @@ batch_size: 100 alignment_requirement: NONE log_level: DEBUG minimizer_index: TEST -nucleotideSequences: +nextclade_sequence_and_datasets: - name: ebola-sudan - name: ebola-zaire organism: multi-ebola-test diff --git a/preprocessing/nextclade/tests/single_segment_config.yaml b/preprocessing/nextclade/tests/single_segment_config.yaml index 2ecb88bc3e..4ec85db1cf 100644 --- a/preprocessing/nextclade/tests/single_segment_config.yaml +++ b/preprocessing/nextclade/tests/single_segment_config.yaml @@ -6,7 +6,7 @@ molecule_type: "genomic RNA" topology: "linear" db_name: "Loculus" minimizer_index: TEST -nucleotideSequences: +nextclade_sequence_and_datasets: - nextclade_dataset_name: ebola-sudan genes: [NPEbolaSudan, VP35EbolaSudan, LEbolaSudan] organism: ebola-sudan-test diff --git a/preprocessing/nextclade/tests/test_metadata_processing_functions.py b/preprocessing/nextclade/tests/test_metadata_processing_functions.py index eab14fb9d9..039f737829 100644 --- a/preprocessing/nextclade/tests/test_metadata_processing_functions.py +++ b/preprocessing/nextclade/tests/test_metadata_processing_functions.py @@ -660,7 +660,7 @@ def test_preprocessing_without_consensus_sequences(config: Config) -> None: ), ) - config.nucleotideSequences = [] + config.nextclade_sequence_and_datasets = [] result = process_all([sequence_entry_data], "temp_dataset_dir", config) processed_entry = result[0].processed_entry From 475c8413f77a6e055207c0e623bc8e6fa08e1482 Mon Sep 17 00:00:00 2001 From: "Anna (Anya) Parker" <50943381+anna-parker@users.noreply.github.com> Date: Tue, 2 Dec 2025 18:04:18 +0100 Subject: [PATCH 35/44] fix(prepro): nextclade dataset tag conversion error (#5614) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolves # ### Screenshot When testing on PPX I see this error caused by https://github.com/loculus-project/loculus/pull/5552 that I sadly missed (string is converted to a list)- this should resolve the bug: ``` INFO:loculus_preprocessing.nextclade:Downloading Nextclade dataset: ['nextclade3', 'dataset', 'get', '--name=nextstrain/rsv/a/EPI_ISL_412866', '--server=https://data.clades.nextstrain.org/v3', '--output-dir=/tmp/tmp6frl2wtu/main', '-', '-', 't', 'a', 'g', '=', '2', '0', '2', '5', '-', '0', '8', '-', '2', '5', '-', '-', '0', '9', '-', '0', '0', '-', '3', '5', 'Z'] error: unexpected argument '-' found ``` ### PR Checklist - [ ] All necessary documentation has been adapted. - [ ] The implemented feature is covered by appropriate, automated tests. - [ ] Any manual testing that has been done is documented (i.e. what exactly was tested?) 🚀 Preview: Add `preview` label to enable --- .../loculus/backend/utils/MetadataEntry.kt | 1 + .../src/loculus_preprocessing/nextclade.py | 24 ++++++++++--------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt b/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt index 43a5431400..da56271756 100644 --- a/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt +++ b/backend/src/main/kotlin/org/loculus/backend/utils/MetadataEntry.kt @@ -84,6 +84,7 @@ fun extractAndValidateFastaIds( fastaIds.toSet() } + false -> { setOf(submissionId) } diff --git a/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py b/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py index 47622a0d1d..33c848dda2 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py @@ -775,19 +775,21 @@ def enrich_with_nextclade( # noqa: PLR0914 def download_nextclade_dataset(dataset_dir: str, config: Config) -> None: for sequence_and_dataset in config.nextclade_sequence_and_datasets: dataset_download_command = [ - "nextclade3", - "dataset", - "get", - f"--name={sequence_and_dataset.nextclade_dataset_name}", - f"--server={ - sequence_and_dataset.nextclade_dataset_server or config.nextclade_dataset_server - }", - f"--output-dir={dataset_dir}/{sequence_and_dataset.name}", - *( + arg + for arg in [ + "nextclade3", + "dataset", + "get", + f"--name={sequence_and_dataset.nextclade_dataset_name}", + f"--server={ + sequence_and_dataset.nextclade_dataset_server or config.nextclade_dataset_server + }", + f"--output-dir={dataset_dir}/{sequence_and_dataset.name}", f"--tag={sequence_and_dataset.nextclade_dataset_tag}" if sequence_and_dataset.nextclade_dataset_tag - else [] - ), + else "", + ] + if arg ] logger.info("Downloading Nextclade dataset: %s", dataset_download_command) if subprocess.run(dataset_download_command, check=False).returncode != 0: # noqa: S603 From 18b8b6789915555c84470a68ecbe4825f8a14bfb Mon Sep 17 00:00:00 2001 From: Fabian Engelniederhammer <92720311+fengelniederhammer@users.noreply.github.com> Date: Wed, 3 Dec 2025 11:19:02 +0100 Subject: [PATCH 36/44] work on integration test comments in #5382 (#5617) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolves these comments: https://github.com/loculus-project/loculus/pull/5382#discussion_r2577181914 https://github.com/loculus-project/loculus/pull/5382#discussion_r2577184365 https://github.com/loculus-project/loculus/pull/5382#discussion_r2577201629 🚀 Preview: Add `preview` label to enable --- .../specs/features/revise-sequence.spec.ts | 22 ++++++++++--------- .../tests/test-helpers/test-data.ts | 20 +++++++++++++++++ 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/integration-tests/tests/specs/features/revise-sequence.spec.ts b/integration-tests/tests/specs/features/revise-sequence.spec.ts index 7836ee8f67..50aa369def 100644 --- a/integration-tests/tests/specs/features/revise-sequence.spec.ts +++ b/integration-tests/tests/specs/features/revise-sequence.spec.ts @@ -7,10 +7,11 @@ import { RevisionPage } from '../../pages/revision.page'; import { NavigationPage } from '../../pages/navigation.page'; import { SingleSequenceSubmissionPage } from '../../pages/submission.page'; import { + CCHF_S_SEGMENT_FULL_SEQUENCE, + createFastaContent, + createRevisionMetadataTsv, createTestMetadata, createTestSequenceData, - createRevisionMetadataTsv, - createFastaContent, EBOLA_SUDAN_SHORT_SEQUENCE, } from '../../test-helpers/test-data'; @@ -39,14 +40,12 @@ sequenceTest( await page.getByRole('link', { name: 'Revise this sequence' }).click({ timeout: 15000 }); await expect(page.getByRole('heading', { name: 'Create new revision from' })).toBeVisible(); - await page.getByTestId(/^discard.*L\)_segment_file$/).click(); - await page.getByTestId(/^discard.*S\)_segment_file$/).click(); - const newSsequence = - 'CAAATGGTTTGAGGAGTTCAAGAAAGGAAATGGACTTGTGGACACTTTCACAAACTCNTATTCCTTTTGTGAAAGCGTNCCAAATCTGGACAGNTTTGTNTTCCAGATGGCNAGTGCCACTGATGATGCACAAAANGANTCCATCTACGCATCTGCNCTGGTGGANGCAACCAAATTTTGTGCACCTATATACGAGTGTGCTTGGGCTAGCTCCACTGGCATTGTTAAAAAGGGACTGGAGTGGTTCGAGAAAAATGCAGGAACCATTAAATCCTGGGATGAGAGTTATACTGAGCTTAAAGTTGAAGTTCCCAAAATAGAACAACTCTCCAACTACCAGCAGGCTGCTCTCAAATGGAGAAAAGACATAGGCTTCCGTGTCAATGCAAATACGGCAGCTTTGAGTAACAAAGTCCTAGCAGAGTACAAAGTTCCTGGCGAGATTGTAATGTCTGTCAAAGAGATGTTGTCAGATATGATTAGAAGNAGGAACCTGATTCTCAACAGAGGTGGTGATGAGAACCCACGCGGCCCAGTTAGCCGTGAACATGTGGAGTGGTGCAGGGAATTCGTCAAAGGCAAGTACATAATGGCTTTCAACCCACCCTGGGGAGACATCAACAAGTCAGGCCGTTCAGGAATAGCACTTGTTGCAACAGGCCTTGCCAAGCTTGCAGAGACTGAAGGGAAGGGAGTTTTTGACGAAGCCAAGAAGACTATAGAGGCTCTTAACGGGTATCTGGACAAGCATAAGGATGAAGTTGACAAAGCAAGTGCCGACAGCATGATAACAAACCTCCTTAAGCACATTGCTAAGGCACAAGAGCTTTACAAAAACTCGTCTGCTCTTCGTGCTCAGGGTGCACAGATTGACACCGTCTTCAGCTCATACTACTGGCTCTACAAGGCCGGTGTGACTCCAGAGACCTTCCCGACTGTTTCACAGTTCCTTTTTGAGTTAGGGAAGCAACCAAGGGGCACCAAGAAAATGAAGAAGGCACTCCTGAGCACCCCAATGAAGTGGGGAAAGAAGCTTTATGAGCTTTTTGCTGATGATTCCTTCCAACAGAACAGGATCTACATGCACCCCGCTGTGCTAACAGCTGGCAGAATCAGTGAAATGGGTGTCTGCTTCGGAACAATCCCTGTGGCCAATCCTGATGATGCCGCCTTAGGATCTGGACACACCAAGTCCATTCTCAACCTTCGGACAAACACTGAGACCAACAATCCGTGTGCCAAGACAATTGTTAAGTTGTTTGAAATTCANAAAACAGGGTTNAACATACAGGACATGGANATTGTGGCCTCNGAGCATCTGCTGCACCAATCCCTTGTTGGCAAGCAGTCTCCATTTCAAAATGCTTACAACGTCAAGGGGAANGCCACCAGTGCCAANATCATCTAAAGCNNANAATNNTCTNCAATCAGCTTTNCC'; + await page.getByTestId(/^discard_fastaHeaderL/).click(); + await page.getByTestId(/^discard_fastaHeaderS/).click(); await page.getByTestId('Add a segment_segment_file').setInputFiles({ name: 'update_S.txt', mimeType: 'text/plain', - buffer: Buffer.from('>S\n' + newSsequence), + buffer: Buffer.from('>S\n' + CCHF_S_SEGMENT_FULL_SEQUENCE), }); await page.getByRole('button', { name: 'Submit' }).click(); @@ -62,9 +61,8 @@ sequenceTest( expect(tabs).toContain('S (unaligned)'); await reviewPage.switchSequenceTab('S (unaligned)'); - const actual = (await reviewPage.getSequenceContent()).replace(/\s+/g, ''); - const expected = newSsequence.replace(/\s+/g, ''); - expect(actual.startsWith(expected)).toBe(true); + const actual = removeWhitespaces(await reviewPage.getSequenceContent()); + expect(actual).toBe(CCHF_S_SEGMENT_FULL_SEQUENCE); await reviewPage.closeSequencesDialog(); }, @@ -121,3 +119,7 @@ groupTest.describe('Bulk sequence revision', () => { expect(overview.total).toBeGreaterThanOrEqual(SEQUENCES_TO_REVISE); }); }); + +function removeWhitespaces(string: string) { + return string.replace(/\s+/g, ''); +} diff --git a/integration-tests/tests/test-helpers/test-data.ts b/integration-tests/tests/test-helpers/test-data.ts index 709113c1ca..7f09ae85fb 100644 --- a/integration-tests/tests/test-helpers/test-data.ts +++ b/integration-tests/tests/test-helpers/test-data.ts @@ -28,6 +28,26 @@ export const EBOLA_SUDAN_FULL_SEQUENCE = 'CAATATCCAACTTCCTGGCAATCAGTTGGACACATGATGGTGATCTTCCGTTTGATGAGAACAAACTTTTTAATCAAGTTCCT' + 'ACTAATACATCAGGGGATGCACATGG'; +export const CCHF_S_SEGMENT_FULL_SEQUENCE = + 'CAAATGGTTTGAGGAGTTCAAGAAAGGAAATGGACTTGTGGACACTTTCACAAACTCNTATTCCTTTTGTGAAAGCGTN' + + 'CCAAATCTGGACAGNTTTGTNTTCCAGATGGCNAGTGCCACTGATGATGCACAAAANGANTCCATCTACGCATCTGCNCT' + + 'GGTGGANGCAACCAAATTTTGTGCACCTATATACGAGTGTGCTTGGGCTAGCTCCACTGGCATTGTTAAAAAGGGACTGG' + + 'AGTGGTTCGAGAAAAATGCAGGAACCATTAAATCCTGGGATGAGAGTTATACTGAGCTTAAAGTTGAAGTTCCCAAAATAG' + + 'AACAACTCTCCAACTACCAGCAGGCTGCTCTCAAATGGAGAAAAGACATAGGCTTCCGTGTCAATGCAAATACGGCAGCT' + + 'TTGAGTAACAAAGTCCTAGCAGAGTACAAAGTTCCTGGCGAGATTGTAATGTCTGTCAAAGAGATGTTGTCAGATATGATT' + + 'AGAAGNAGGAACCTGATTCTCAACAGAGGTGGTGATGAGAACCCACGCGGCCCAGTTAGCCGTGAACATGTGGAGTGGTGC' + + 'AGGGAATTCGTCAAAGGCAAGTACATAATGGCTTTCAACCCACCCTGGGGAGACATCAACAAGTCAGGCCGTTCAGGAATA' + + 'GCACTTGTTGCAACAGGCCTTGCCAAGCTTGCAGAGACTGAAGGGAAGGGAGTTTTTGACGAAGCCAAGAAGACTATAGAG' + + 'GCTCTTAACGGGTATCTGGACAAGCATAAGGATGAAGTTGACAAAGCAAGTGCCGACAGCATGATAACAAACCTCCTTAAG' + + 'CACATTGCTAAGGCACAAGAGCTTTACAAAAACTCGTCTGCTCTTCGTGCTCAGGGTGCACAGATTGACACCGTCTTCAGC' + + 'TCATACTACTGGCTCTACAAGGCCGGTGTGACTCCAGAGACCTTCCCGACTGTTTCACAGTTCCTTTTTGAGTTAGGGAAG' + + 'CAACCAAGGGGCACCAAGAAAATGAAGAAGGCACTCCTGAGCACCCCAATGAAGTGGGGAAAGAAGCTTTATGAGCTTTTT' + + 'GCTGATGATTCCTTCCAACAGAACAGGATCTACATGCACCCCGCTGTGCTAACAGCTGGCAGAATCAGTGAAATGGGTGTC' + + 'TGCTTCGGAACAATCCCTGTGGCCAATCCTGATGATGCCGCCTTAGGATCTGGACACACCAAGTCCATTCTCAACCTTCGG' + + 'ACAAACACTGAGACCAACAATCCGTGTGCCAAGACAATTGTTAAGTTGTTTGAAATTCANAAAACAGGGTTNAACATACAG' + + 'GACATGGANATTGTGGCCTCNGAGCATCTGCTGCACCAATCCCTTGTTGGCAAGCAGTCTCCATTTCAAAATGCTTACAAC' + + 'GTCAAGGGGAANGCCACCAGTGCCAANATCATCTAAAGCNNANAATNNTCTNCAATCAGCTTTNCC'; + /** * Helper function to create standard test metadata with optional overrides */ From 21a9df8388cd9dbed62ba1674596e8be995406ab Mon Sep 17 00:00:00 2001 From: anna-parker <50943381+anna-parker@users.noreply.github.com> Date: Wed, 3 Dec 2025 13:41:00 +0100 Subject: [PATCH 37/44] refactor(backend): rename addFastaId to requireConsensusSequence --- .../main/kotlin/org/loculus/backend/model/SubmitModel.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt b/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt index 0c0da96a5d..df20c3f865 100644 --- a/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt +++ b/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt @@ -171,7 +171,7 @@ class SubmitModel( metadataFileTypes, metadataTempFileToDelete, ) - val addFastaId = requiresConsensusSequenceFile(submissionParams.organism) + val requireConsensusSequence = requiresConsensusSequenceFile(submissionParams.organism) try { uploadMetadata(uploadId, submissionParams, metadataStream, batchSize) } finally { @@ -180,14 +180,14 @@ class SubmitModel( val sequenceFile = submissionParams.sequenceFile if (sequenceFile == null) { - if (addFastaId) { + if (requireConsensusSequence) { throw BadRequestException( "Submissions for organism ${submissionParams.organism.name} require a sequence file.", ) } return } - if (!addFastaId) { + if (!requireConsensusSequence) { throw BadRequestException( "Sequence uploads are not allowed for organism ${submissionParams.organism.name}.", ) From a27144e4ed3d98d574259104b5298b93085a3e7d Mon Sep 17 00:00:00 2001 From: anna-parker <50943381+anna-parker@users.noreply.github.com> Date: Wed, 3 Dec 2025 13:41:34 +0100 Subject: [PATCH 38/44] refactor: use minimizer_url instead of minimizer_index in both ingest and preprocessing --- ingest/Snakefile | 6 +++--- ingest/scripts/parse_nextclade_sort_output.py | 6 +++--- kubernetes/loculus/values.schema.json | 4 ++-- kubernetes/loculus/values.yaml | 4 ++-- .../nextclade/src/loculus_preprocessing/backend.py | 8 ++++---- .../nextclade/src/loculus_preprocessing/config.py | 2 +- .../nextclade/src/loculus_preprocessing/nextclade.py | 2 +- .../nextclade/src/loculus_preprocessing/prepro.py | 2 +- preprocessing/nextclade/tests/multi_pathogen_config.yaml | 2 +- preprocessing/nextclade/tests/multi_segment_config.yaml | 2 +- .../nextclade/tests/multi_segment_config_unaligned.yaml | 2 +- preprocessing/nextclade/tests/single_segment_config.yaml | 2 +- 12 files changed, 21 insertions(+), 21 deletions(-) diff --git a/ingest/Snakefile b/ingest/Snakefile index ee340fddb8..61d08f795a 100644 --- a/ingest/Snakefile +++ b/ingest/Snakefile @@ -75,7 +75,7 @@ if SEGMENTED: APPLY_MINIMIZER = ( SEGMENTED and segment_detection_method == SegmentDetectionMethod.MINIMIZER -) or (config.get("minimizer_index") and config.get("minimizer_parser")) +) or (config.get("minimizer_url") and config.get("minimizer_parser")) print(segment_detection_method) @@ -286,9 +286,9 @@ if APPLY_MINIMIZER: results="results/minimizer.json", params: minimizer=( - config.get("segment_identification").get("minimizer_index") + config.get("segment_identification").get("minimizer_url") if config.get("segment_identification") - else config.get("minimizer_index") + else config.get("minimizer_url") ), shell: """ diff --git a/ingest/scripts/parse_nextclade_sort_output.py b/ingest/scripts/parse_nextclade_sort_output.py index 104b6e00d2..74695de237 100644 --- a/ingest/scripts/parse_nextclade_sort_output.py +++ b/ingest/scripts/parse_nextclade_sort_output.py @@ -23,7 +23,7 @@ @dataclass class NextcladeSortParams: - minimizer_index: str + minimizer_url: str minimizer_parser: list[str] method: str = "minimizer" @@ -32,7 +32,7 @@ class NextcladeSortParams: class Config: segment_identification: NextcladeSortParams nucleotide_sequences: list[str] - minimizer_index: str | None + minimizer_url: str | None minimizer_parser: list[str] | None segmented: bool = False @@ -88,7 +88,7 @@ def main(config_file: str, sort_results: str, output: str, log_level: str) -> No relevant_config = {key: full_config.get(key, []) for key in Config.__annotations__} if not relevant_config["segmented"]: relevant_config["segment_identification"] = NextcladeSortParams( - relevant_config["minimizer_index"], relevant_config["minimizer_parser"] + relevant_config["minimizer_url"], relevant_config["minimizer_parser"] ) else: relevant_config["segment_identification"] = NextcladeSortParams( diff --git a/kubernetes/loculus/values.schema.json b/kubernetes/loculus/values.schema.json index 56cc505de9..1d0a253966 100644 --- a/kubernetes/loculus/values.schema.json +++ b/kubernetes/loculus/values.schema.json @@ -666,7 +666,7 @@ "type": "boolean", "description": "If true run nextclade sort and require that the highest scoring match is in the config.accepted_dataset_matches" }, - "minimizer_index": { + "minimizer_url": { "groups": ["nextcladePipelineConfigFile"], "docsIncludePrefix": false, "type": "string", @@ -741,7 +741,7 @@ "type": "array", "description": "Required if method is minimizer, list of the name of each '_' - separated metadata field in the minimizer index" }, - "minimizer_index": { + "minimizer_url": { "groups": ["ingestPipelineConfigFile"], "docsIncludePrefix": false, "type": "string", diff --git a/kubernetes/loculus/values.yaml b/kubernetes/loculus/values.yaml index 51c4cbb57e..4d7b19c995 100644 --- a/kubernetes/loculus/values.yaml +++ b/kubernetes/loculus/values.yaml @@ -1756,7 +1756,7 @@ defaultOrganisms: configFile: <<: *preprocessingConfigFile log_level: DEBUG - minimizer_index: https://loculus-project.github.io/nextclade-sort-minimizers/data/CCHF/minimizer.json + minimizer_url: https://loculus-project.github.io/nextclade-sort-minimizers/data/CCHF/minimizer.json nextclade_sequence_and_datasets: - name: L nextclade_dataset_name: community/pathoplexus/cchfv/L @@ -1882,7 +1882,7 @@ defaultOrganisms: configFile: <<: *preprocessingConfigFile segment_classification_method: "minimizer" - minimizer_index: "https://raw.githubusercontent.com/alejandra-gonzalezsanchez/loculus-evs/master/evs_minimizer-index.json" + minimizer_url: "https://raw.githubusercontent.com/alejandra-gonzalezsanchez/loculus-evs/master/evs_minimizer-index.json" nextclade_sequence_and_datasets: - name: CV-A16 nextclade_dataset_name: enpen/enterovirus/cv-a16 diff --git a/preprocessing/nextclade/src/loculus_preprocessing/backend.py b/preprocessing/nextclade/src/loculus_preprocessing/backend.py index 01010045d2..43f3ff8051 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/backend.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/backend.py @@ -222,12 +222,12 @@ def upload_embl_file_to_presigned_url(content: str, url: str) -> None: def download_minimizer(config, save_path): - if config.minimizer_index: - url = config.minimizer_index + if config.minimizer_url: + url = config.minimizer_url elif config.nextclade_dataset_server: - url = config.nextclade_dataset_server.rstrip("/") + "/minimizer_index.json" + url = config.nextclade_dataset_server.rstrip("/") + "/minimizer_url.json" else: - msg = "Cannot download minimizer: no minimizer_index or nextclade_dataset_server specified in config" + msg = "Cannot download minimizer: no minimizer_url or nextclade_dataset_server specified in config" logger.error(msg) raise RuntimeError(msg) diff --git a/preprocessing/nextclade/src/loculus_preprocessing/config.py b/preprocessing/nextclade/src/loculus_preprocessing/config.py index 9b0cfe3422..0717cfd3c5 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/config.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/config.py @@ -77,7 +77,7 @@ class Config: nextclade_dataset_server: str = "https://data.clades.nextstrain.org/v3" require_nextclade_sort_match: bool = False - minimizer_index: str | None = None + minimizer_url: str | None = None create_embl_file: bool = False scientific_name: str = "Orthonairovirus haemorrhagiae" diff --git a/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py b/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py index 33c848dda2..f7e175c6b7 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/nextclade.py @@ -161,7 +161,7 @@ def run_sort( ) -> pd.DataFrame: """ Run nextclade - - use config.minimizer_index or default minimizer from nextclade server + - use config.minimizer_url or default minimizer from nextclade server """ subprocess_args = [ arg diff --git a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py index 33db7990b0..fc6052a94b 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py @@ -613,7 +613,7 @@ def run(config: Config) -> None: if config.alignment_requirement != AlignmentRequirement.NONE: download_nextclade_dataset(dataset_dir, config) if ( - config.minimizer_index + config.minimizer_url or config.segment_classification_method == SegmentClassificationMethod.MINIMIZER or config.require_nextclade_sort_match ): diff --git a/preprocessing/nextclade/tests/multi_pathogen_config.yaml b/preprocessing/nextclade/tests/multi_pathogen_config.yaml index e1ae34c8ec..463c002740 100644 --- a/preprocessing/nextclade/tests/multi_pathogen_config.yaml +++ b/preprocessing/nextclade/tests/multi_pathogen_config.yaml @@ -2,7 +2,7 @@ batch_size: 100 alignment_requirement: ALL log_level: DEBUG -minimizer_index: TEST +minimizer_url: TEST nextclade_dataset_server: TEST nextclade_sequence_and_datasets: - name: ebola-sudan diff --git a/preprocessing/nextclade/tests/multi_segment_config.yaml b/preprocessing/nextclade/tests/multi_segment_config.yaml index 27b67f73f2..192f0b2dc1 100644 --- a/preprocessing/nextclade/tests/multi_segment_config.yaml +++ b/preprocessing/nextclade/tests/multi_segment_config.yaml @@ -1,7 +1,7 @@ batch_size: 100 alignment_requirement: ALL log_level: DEBUG -minimizer_index: TEST +minimizer_url: TEST segment_classification_method: "minimizer" nextclade_sequence_and_datasets: - name: ebola-sudan diff --git a/preprocessing/nextclade/tests/multi_segment_config_unaligned.yaml b/preprocessing/nextclade/tests/multi_segment_config_unaligned.yaml index 9d6fc1eae1..2d4dc3640f 100644 --- a/preprocessing/nextclade/tests/multi_segment_config_unaligned.yaml +++ b/preprocessing/nextclade/tests/multi_segment_config_unaligned.yaml @@ -1,7 +1,7 @@ batch_size: 100 alignment_requirement: NONE log_level: DEBUG -minimizer_index: TEST +minimizer_url: TEST nextclade_sequence_and_datasets: - name: ebola-sudan - name: ebola-zaire diff --git a/preprocessing/nextclade/tests/single_segment_config.yaml b/preprocessing/nextclade/tests/single_segment_config.yaml index 4ec85db1cf..a223e18d71 100644 --- a/preprocessing/nextclade/tests/single_segment_config.yaml +++ b/preprocessing/nextclade/tests/single_segment_config.yaml @@ -5,7 +5,7 @@ scientific_name: "Test Ebola Sudan Virus" molecule_type: "genomic RNA" topology: "linear" db_name: "Loculus" -minimizer_index: TEST +minimizer_url: TEST nextclade_sequence_and_datasets: - nextclade_dataset_name: ebola-sudan genes: [NPEbolaSudan, VP35EbolaSudan, LEbolaSudan] From 6d2bb1e97800d620b9d285ddeb892ed5c7fd97f9 Mon Sep 17 00:00:00 2001 From: anna-parker <50943381+anna-parker@users.noreply.github.com> Date: Wed, 3 Dec 2025 13:46:51 +0100 Subject: [PATCH 39/44] add back genes missing from readme --- preprocessing/nextclade/README.md | 2 ++ preprocessing/nextclade/src/loculus_preprocessing/backend.py | 2 +- preprocessing/nextclade/tests/multi_pathogen_config.yaml | 2 +- preprocessing/nextclade/tests/multi_segment_config.yaml | 2 +- preprocessing/nextclade/tests/single_segment_config.yaml | 3 +-- preprocessing/nextclade/tests/test_nextclade_preprocessing.py | 1 + 6 files changed, 7 insertions(+), 5 deletions(-) diff --git a/preprocessing/nextclade/README.md b/preprocessing/nextclade/README.md index b89f357deb..95decb7668 100644 --- a/preprocessing/nextclade/README.md +++ b/preprocessing/nextclade/README.md @@ -227,6 +227,7 @@ To add multiple preprocessing pipelines alter the preprocessing section of the ` nextclade_sequence_and_datasets: - name: main # default value, not actually required nextclade_dataset_name: nextstrain/wnv/all-lineages + genes: [capsid, prM, env, NS1, NS2A, NS2B, NS3, NS4A, 2K, NS4B, NS5] batch_size: 100 - image: ghcr.io/loculus-project/preprocessing-nextclade args: @@ -237,5 +238,6 @@ To add multiple preprocessing pipelines alter the preprocessing section of the ` nextclade_sequence_and_datasets: - name: main nextclade_dataset_name: nextstrain/wnv/all-lineages + genes: [capsid, prM, env, NS1, NS2A, NS2B, NS3, NS4A, 2K, NS4B, NS5] batch_size: 100 ``` diff --git a/preprocessing/nextclade/src/loculus_preprocessing/backend.py b/preprocessing/nextclade/src/loculus_preprocessing/backend.py index 43f3ff8051..07d19facca 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/backend.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/backend.py @@ -225,7 +225,7 @@ def download_minimizer(config, save_path): if config.minimizer_url: url = config.minimizer_url elif config.nextclade_dataset_server: - url = config.nextclade_dataset_server.rstrip("/") + "/minimizer_url.json" + url = config.nextclade_dataset_server.rstrip("/") + "/minimizer_index.json" else: msg = "Cannot download minimizer: no minimizer_url or nextclade_dataset_server specified in config" logger.error(msg) diff --git a/preprocessing/nextclade/tests/multi_pathogen_config.yaml b/preprocessing/nextclade/tests/multi_pathogen_config.yaml index 463c002740..5fd219c9e1 100644 --- a/preprocessing/nextclade/tests/multi_pathogen_config.yaml +++ b/preprocessing/nextclade/tests/multi_pathogen_config.yaml @@ -8,7 +8,7 @@ nextclade_sequence_and_datasets: - name: ebola-sudan nextclade_dataset_name: ebola-dataset/ebola-sudan accepted_sort_matches: ebola-sudan - genes: [NPEbolaSudan, VP35EbolaSudan, LEbolaSudan] + genes: [NPEbolaSudan, VP35EbolaSudan] - name: ebola-zaire nextclade_dataset_name: ebola-dataset/ebola-zaire accepted_sort_matches: ebola-zaire diff --git a/preprocessing/nextclade/tests/multi_segment_config.yaml b/preprocessing/nextclade/tests/multi_segment_config.yaml index 192f0b2dc1..7594f5157c 100644 --- a/preprocessing/nextclade/tests/multi_segment_config.yaml +++ b/preprocessing/nextclade/tests/multi_segment_config.yaml @@ -6,7 +6,7 @@ segment_classification_method: "minimizer" nextclade_sequence_and_datasets: - name: ebola-sudan nextclade_dataset_name: ebola-dataset/ebola-sudan - genes: [NPEbolaSudan, VP35EbolaSudan, LEbolaSudan] + genes: [NPEbolaSudan, VP35EbolaSudan] - name: ebola-zaire nextclade_dataset_name: ebola-dataset/ebola-zaire genes: [VP24EbolaZaire, LEbolaZaire] diff --git a/preprocessing/nextclade/tests/single_segment_config.yaml b/preprocessing/nextclade/tests/single_segment_config.yaml index a223e18d71..358998ca5f 100644 --- a/preprocessing/nextclade/tests/single_segment_config.yaml +++ b/preprocessing/nextclade/tests/single_segment_config.yaml @@ -5,10 +5,9 @@ scientific_name: "Test Ebola Sudan Virus" molecule_type: "genomic RNA" topology: "linear" db_name: "Loculus" -minimizer_url: TEST nextclade_sequence_and_datasets: - nextclade_dataset_name: ebola-sudan - genes: [NPEbolaSudan, VP35EbolaSudan, LEbolaSudan] + genes: [NPEbolaSudan, VP35EbolaSudan] organism: ebola-sudan-test processing_spec: completeness: diff --git a/preprocessing/nextclade/tests/test_nextclade_preprocessing.py b/preprocessing/nextclade/tests/test_nextclade_preprocessing.py index 24e3c5f1bf..8df97884bd 100644 --- a/preprocessing/nextclade/tests/test_nextclade_preprocessing.py +++ b/preprocessing/nextclade/tests/test_nextclade_preprocessing.py @@ -1042,6 +1042,7 @@ def test_preprocessing_single_segment(test_case_def: Case): def test_preprocessing_single_segment_with_require_nextclade_sort_match(test_case_def: Case): config = get_config(SINGLE_SEGMENT_CONFIG, ignore_args=True) config.require_nextclade_sort_match = True + config.minimizer_url = "TEST" # will use minimizer in EBOLA_SUDAN_DATASET factory_custom = ProcessedEntryFactory(all_metadata_fields=list(config.processing_spec.keys())) test_case = test_case_def.create_test_case(factory_custom) processed_entry = process_single_entry(test_case, config, EBOLA_SUDAN_DATASET) From 3e84bb8b28f768ef0dc589733a1b11e2579e5e9c Mon Sep 17 00:00:00 2001 From: anna-parker <50943381+anna-parker@users.noreply.github.com> Date: Wed, 3 Dec 2025 14:48:21 +0100 Subject: [PATCH 40/44] merge conflict: update isMultiSegmentedOrganism to use getReferenceGenomeLightweightSchema --- website/src/config.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/website/src/config.ts b/website/src/config.ts index a869487f94..fd6eef9f67 100644 --- a/website/src/config.ts +++ b/website/src/config.ts @@ -223,9 +223,16 @@ export function getSubmissionIdInputFields(isMultiSegmented: boolean): InputFiel } export function isMultiSegmentedOrganism(organism: string): boolean { - const referenceGenomes = getReferenceGenome(organism); - const segmentNames = referenceGenomes.nucleotideSequences.map((s) => s.name); - return segmentNames.length > 1; + const referenceGenomeLightweightSchema = getReferenceGenomeLightweightSchema(organism); + const numberOfRows = Math.max( + ...Object.values(referenceGenomeLightweightSchema).map( + (suborganismSchema) => suborganismSchema.nucleotideSegmentNames.length, + ), + ); + if (numberOfRows > 1) { + return true; + } + return false; } export function getGroupedInputFields( From 2213cecda0ff59d4380a580dcf6c6f5bb1886b75 Mon Sep 17 00:00:00 2001 From: anna-parker <50943381+anna-parker@users.noreply.github.com> Date: Wed, 3 Dec 2025 14:53:29 +0100 Subject: [PATCH 41/44] format --- website/src/config.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/website/src/config.ts b/website/src/config.ts index fd6eef9f67..28fcc8a23c 100644 --- a/website/src/config.ts +++ b/website/src/config.ts @@ -225,14 +225,14 @@ export function getSubmissionIdInputFields(isMultiSegmented: boolean): InputFiel export function isMultiSegmentedOrganism(organism: string): boolean { const referenceGenomeLightweightSchema = getReferenceGenomeLightweightSchema(organism); const numberOfRows = Math.max( - ...Object.values(referenceGenomeLightweightSchema).map( - (suborganismSchema) => suborganismSchema.nucleotideSegmentNames.length, - ), - ); + ...Object.values(referenceGenomeLightweightSchema).map( + (suborganismSchema) => suborganismSchema.nucleotideSegmentNames.length, + ), + ); if (numberOfRows > 1) { - return true; - } - return false; + return true; + } + return false; } export function getGroupedInputFields( From 35ff38f997a9c4fcb2dfece31c8b3d2c2fecd04d Mon Sep 17 00:00:00 2001 From: anna-parker <50943381+anna-parker@users.noreply.github.com> Date: Thu, 4 Dec 2025 18:44:16 +0100 Subject: [PATCH 42/44] format --- preprocessing/nextclade/src/loculus_preprocessing/prepro.py | 1 - 1 file changed, 1 deletion(-) diff --git a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py index fc6052a94b..9516dccc4e 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py @@ -173,7 +173,6 @@ def add_assigned_segment( return InputData(datum=valid_segments[0]) - def add_assigned_segment( unprocessed: UnprocessedAfterNextclade, config: Config, From 9a7c376b368c6da783af14ba5a39d03f9f4490ba Mon Sep 17 00:00:00 2001 From: anna-parker <50943381+anna-parker@users.noreply.github.com> Date: Thu, 4 Dec 2025 18:45:12 +0100 Subject: [PATCH 43/44] merge conflict --- .../src/loculus_preprocessing/prepro.py | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py index 9516dccc4e..3bc4cc009f 100644 --- a/preprocessing/nextclade/src/loculus_preprocessing/prepro.py +++ b/preprocessing/nextclade/src/loculus_preprocessing/prepro.py @@ -173,27 +173,6 @@ def add_assigned_segment( return InputData(datum=valid_segments[0]) -def add_assigned_segment( - unprocessed: UnprocessedAfterNextclade, - config: Config, -) -> InputData: - if not unprocessed.nextcladeMetadata: - return InputData(datum=None) - valid_segments = [key for key, value in unprocessed.nextcladeMetadata.items() if value] - if not valid_segments: - return InputData(datum=None) - if len(valid_segments) > 1: - return InputData( - datum=None, - errors=[ - MultipleValidSegmentsError(valid_segments).getProcessingAnnotation( - processed_field_name="ASSIGNED_SEGMENT", organism=config.organism - ) - ], - ) - return InputData(datum=valid_segments[0]) - - def add_input_metadata( spec: ProcessingSpec, unprocessed: UnprocessedAfterNextclade, From 3c772863c57b7686a7a9d04bd3cbb8bc9357833f Mon Sep 17 00:00:00 2001 From: "Anna (Anya) Parker" <50943381+anna-parker@users.noreply.github.com> Date: Fri, 5 Dec 2025 14:28:41 +0100 Subject: [PATCH 44/44] feat(website): edit page - do not include fasta description in metadata field `fastaIds` (#5629) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit resolves https://github.com/loculus-project/loculus/issues/5627 The `fastaIds` object should contain a space separated list of fasta IDs. The website was constructing `fastaIds` for submissions via the edit page by concatenating full fasta headers of the form `>fastaId description` (and removing `>`) - but this lead to the backend thinking the description was another fastaId. Also adds a description to multiple fasta files in the integration tests to ensure in future such an issue cannot be missed ### Screenshot ### PR Checklist - [ ] All necessary documentation has been adapted. - [ ] The implemented feature is covered by appropriate, automated tests. - [ ] Any manual testing that has been done is documented (i.e. what exactly was tested?) 🚀 Preview: https://fastaheader.loculus.org --------- Co-authored-by: Cornelius Roemer Co-authored-by: Fabian Engelniederhammer <92720311+fengelniederhammer@users.noreply.github.com> --- .../specs/features/revise-sequence.spec.ts | 4 +- .../test-data/cchfv_test_sequences.fasta | 2 +- .../components/Edit/SequencesForm.spec.tsx | 125 +++++++++++++----- website/src/components/Edit/SequencesForm.tsx | 18 ++- .../FileUpload/fileProcessing.spec.ts | 6 +- .../Submission/FileUpload/fileProcessing.ts | 6 +- 6 files changed, 118 insertions(+), 43 deletions(-) diff --git a/integration-tests/tests/specs/features/revise-sequence.spec.ts b/integration-tests/tests/specs/features/revise-sequence.spec.ts index 50aa369def..afe1572ce1 100644 --- a/integration-tests/tests/specs/features/revise-sequence.spec.ts +++ b/integration-tests/tests/specs/features/revise-sequence.spec.ts @@ -45,7 +45,7 @@ sequenceTest( await page.getByTestId('Add a segment_segment_file').setInputFiles({ name: 'update_S.txt', mimeType: 'text/plain', - buffer: Buffer.from('>S\n' + CCHF_S_SEGMENT_FULL_SEQUENCE), + buffer: Buffer.from('>S description\n' + CCHF_S_SEGMENT_FULL_SEQUENCE), }); await page.getByRole('button', { name: 'Submit' }).click(); @@ -100,7 +100,7 @@ groupTest.describe('Bulk sequence revision', () => { const revisionMetadata = createRevisionMetadataTsv(accessionsToRevise, baseSubmissionId); const revisedSequences = accessionsToRevise.map((accession, i) => ({ - id: `${baseSubmissionId}-${i}`, + id: `${baseSubmissionId}-${i} description`, sequence: EBOLA_SUDAN_SHORT_SEQUENCE + 'GGGGGG', })); const fastaContent = createFastaContent(revisedSequences); diff --git a/integration-tests/tests/test-data/cchfv_test_sequences.fasta b/integration-tests/tests/test-data/cchfv_test_sequences.fasta index 2406f4adf4..606166f744 100644 --- a/integration-tests/tests/test-data/cchfv_test_sequences.fasta +++ b/integration-tests/tests/test-data/cchfv_test_sequences.fasta @@ -1,4 +1,4 @@ ->test_NIHPAK-19_L +>test_NIHPAK-19_L description to be ignored CCACATTGACACAGANAGCTCCAGTAGTGGTTCTCTGTCCTTATTAAACCATGGACTTCTTAAGAAACCTTGACTGGACTCAGGTGATTGCTAGTCAGTATGTGACCAATCCCAGGTTTAATATCTCTGATTACTTCGAGATTGTTCGACAGCCTGGTGACGGGAACTGTTTCTACCACAGTATAGCTGAGTTAACCATGCCCAACAAAACAGATCACTCATACCATAACATCAAACATCTGACTGAGGTGGCAGCACGGAAGTATTATCAGGAGGAGCCGGAGGCTAAGCTCATTGGCCTGAGTCTGGAAGACTATCTTAAGAGGATGCTATCTGACAACGAATGGGGATCGACTCTTGAGGCATCTATGTTGGCTAAGGAAATGGGTATTACTATCATCATTTGGACTGTTGCAGCCAGTGACGAAGTGGAAGCAGGCATAAAGTTTGGTGATGGTGATGTGTTTACAGCCGTGAATCTTCTGCACTCCGGACAGACACACTTTGATGCCCTCAGAATACTGCCNCANTTTGAGGCTGACACAAGAGAGNCCTTNAGTCTGGTAGACAANNTNATAGCTGTGGACCANNTGACCTCNTCTTCAAGTGATGAANTGCAGGACTANGAAGANCTTGCTTTAGCACTTACNAGNGCGGAAGAACCATNTAGACGGTCTAGCNTGGATGAGGTNACCCTNTCTAAGAAACAAGCAGAGNTATTGAGGCAGAAGGCATCTCAGTTGTCNAAACTGGTTAATAAAAGTCAGAACATACCGACTAGAGTTGGCAGGGTTCTGGACTGTATGTTTAACTGCAAACTATGTGTTGAAATATCAGCTGACACTCTAATTCTGCGACCAGAATCTAAAGAAAGAATTGGTGAAGTTATGTCGTTGCGACAGCTAGGTCACAAATTGCTAACACGAGATAAACAAATTAAGCAGGAGTTCTCTAGAATGAAGCTCTATGTTACCAAAGATCTGCTTGANCATCTNGATGTTGGTGGGNTNTTNAGAGCAGCCTTCCCTGGAACAGGGATAGANAGACATATGCAGNTGNTACANTCTGAAATGATACTGGACATTTGTACNGTNTCACTTGGCGTCATGTTATCAACATTCTTATACGGCTCTAACAACAAAAACAAGAAAAAATTCATCACCAACTGCTTGCTTAGCACAGCCNTGTCTGGNAAGAAGGTGTACAAGGTTCTTGGTAACTTAGGNAATGAACTGTTNTATAANGCACCNAGAAAGGCCTTNGCAACCGTCTGTNGTGCNCTNTTTGGNAAACANNTAAACAAGCTTCAGAACTGCTTCAGGACTATAAGTCCTGTTAGCCTGCTTGCACTAAGAAACCTGGACTTTGACTGTCTTAGTGTGCAAGACTACAATGGTATGATAGAAAATATGTCCAAATTGGACAACACAGATGTTGAATTCAACCACAGAGAAATAGCCGATCTCAACCAGTTAACTTCTCGGCTTATCACATTGAGGAAAGAGAAAGACACTGATCTCCTCAAGCAATGGTTTCCTGAGGGTGATCTCACTCGTAGAAGCACCAGGAACGTTGCAAATGCAGAAGAATTTGTCATATCTGAATTCTTTAAGAGGAAAGACATTATGAAGTTTATCAGCACCTCAGGAAGAGCAATGAGTGCAGGCAAAATTGGTAATGTCCTTTCCTATGCACATAACCTTTATTTGAGCAAGTCTAGCCTAAACATGACTTCTGAAGATATTTCACAGCTTCTAATCGAGATTAAGAGGCTGTATGCTCTACAAGAAGATTCTGAAGTAGAGCCAATAGCCATAATTTGTGATGGCATTGAGAGCAACATGAAGCAGTTATTTGCTATATTNCCTCCNGACTGTGCAAGAGAGTGNGANGTTCTCTTTGATGACATAAGAAANTCTCCAACGCACAGTACAGCNTGGAAGCATGCCCTTCGATTAAAGGGNACNGCATATGAGGGTNTNTTTGCCAACTGNTATGGATGGCANTANATCCCGGAAGACATTAAACCAAGCTTGACCATGTTGATACAGACATTGTTCCCTGACAAATTTGAAGATTTTTTGGACCGAACTCAATTACATCCAGAGTTCAGAGATTTAACTCCTGACTTTTCGCTTACACAAAAGGTTCATTTCAAAAGGAATCAGATACCCAGTGTTGAAAACGTTCAGATCTCCATAGATGCATCATTGCCTGAATCTGTGGAGGCAGTGCCAGTGACAGAAAGGAAAATGTTCCCCCTACCTGAGACTCCATTAAGTGAGGTACATTCAATAGAGCGCATCATGGAAAATTTCACTCGCCTCATGCATGAAGGAAAGCCTTCAACCAAGAGAAAAGATGAAGATTCAACAGAACAGAACGGTCAGCAGAATACTGCTGAACATGAGAGTTCAAGCATCTTGACTTTTAAGGACTATGGAGAGAGGGGAATAGTTGAGGAGAATCACATGAGGTTCAGTGAAGAGGATCAGCTGGAAACNAGNCAGTTGTTGTTGGTAGAAGTTGGTTTTCAAACTGANATTGATGGGAAAATAAGAACAGACCANAAAAAATGGAAAGATATATTAAAGCTGTTNGAGTTACTNGGAATNAAGTGCTCATTCATTGCCTGTGCTGACTGNTCGNCTACACCNCCAGACAGATGGTGGATTACTGAGGACAGAGTACGAGTCTTAAAGAACTCAGTCAGCTTTCTCTTCAACAAACTCTCCAGGAACTCACCTACAGAGGTTACTGACATAGTTGTTGGGGCCATAAGTACTCAAAAGGTTAGAAGTTACTTAAANGCAGGGACTGCNACAAAAACCCCTGTGTCAACNAAAGANGTTCTAGAGACTTGGGAAAGAATGAAAGANCATATACTTAACAGGCCNACAGGTTTNACACTNCCNNCCAGTTTGGAGCAGGCAATGCGCAAAGGANTAGTCGAGGGNGTGGTNATCTCCAAGGNGGGCTCTGAATCNTGCATNNANATGTTNAAGGAAAANTTGGACCGAATAACTGATGAATTTGAGCGGACGAAATTTAAACATGAGCTTACNCAGAATGTTACTACTNGTGAAAAGATGCTGTTAAGTTGGTTAAGTGAAGACATAAAATCATCGAGATGTAGTGAGTGTCTTGCTAATATAAAGAAAACTGTTGATGAGACTGCCAACCTATCAGAGAAGATCGAACTGCTTGCTTATAATTTGCAACTTACTAGTCACTGTGGTAACTGTCACCCCAACGGTGTGAACATTAGCAACATATCAAATGTATGCAAGAGGTGTCCCAAGATAGAAGTGGTCAGTCATTGTGAGAACAAAGGTTTTGAGGATAGCAATGAATGCTTAACAGACTTAGACAGACTTGTTAGACTCACATTACCGGGAAAAACTGAGAAAGAGAGAAGAGTCAAGCGTAATGTAGAATATCTGATAAAATTGATGATGAGCATGTCAGGCATCGACTGTATAAAGTATCCTACAGGACAGCTTATTACTCATGGNAGNGTAAGTGCAAAGCANAATGATGGAANCNTGAAAGATAGGAGTGATGACGACCAAAGACTNGCTGAGAAGANAGATACTGTNAGNAAAGAGCTTTCNGAAACNAANNTNAAAGANTATTCANCNTATGCAAGAGGNGTAATCTCAAATTCGCTAAAAAACCTCTCGAAGCAAGGCAAATCAAAGTGNTCTGTGCCAAGATCTTGGCTTGAAAAGATACTGTTTGACTTAAAAGTGCCCACTAAAGACGAAGAAGTGCTGATAAACATCAGGAATTCACTGAAGGCTAGATCTGAGTATGTTAGAAACAANGACAAACTACTNATAAGATCCAANGAAGAACTCAAAAAATGTTTCGATGCGCAGTCTTTTAAATTGATGAAAAACAAACAACCTGTGCCTTTTCANGTTGANTGTATACTGTTTAAGGAAGTGGCAGCAGAGTGCATGAAAAGATATATTGGCACACCTTATGAAGGAATTATAGANACTTTAGTNTCNTTAATCAATGTGTTAACAAGATTCACTTGGTTTCAGGANGTAGTGCTNTATGGTAAGATATGTGANACCTTCTTAAGGTGNTGCACNGAATTCAATAGATCAGGGGTTAAGCTAGTNAAGGTAAGNCACTGTGANATTAACNTATCAGTCAAGCTGCCATCAAACAAAAANGAGAANATGTTATGTTGNNTATACAGTAGTAACATGGAGCTCTTACANGGACCTTTCTANTTGAACAGGAGGCAAGCTGTCCTTGGCTCATCATACCTTTACATNGTCATTACGCTNTACATACAAGTGCTGCAGCAGTACAGGTGTCTAGAAGTCATAAATAGCGTGAATGAGAAAACATTGCAGGACATTGAAAATCACTCTATGACTCTGCTAGAAGATGCATTCAAAGAACTTACTTCTGCGCTTGAGGGTAGATTTGAAGAATCTTACAAAGTACGAACTTCAAGGTGCAAAGCTAGCGGAAATTTCTTAAACAGAAGCAGTAGAGACCACTTTATAAGTATTGTTTCAGGCTTAAACCTGGTTTATGGCTTCCTCATGAAAGATAACTTATTGGCCAACTCTCAGCAACAAAACAAACAACTTCAAATGCTTCGTTTTGGTATGCTTGCAGGGCTTAGTAGGCTTGTCTGTCCTAATGAGTTGGGAAAAAAGTTTTCAACAAGTTGTAGAAGAATTGAAGATAACATTGCAAGACTTTACTTACAAACGTCTATATACTGNTCAGTTAGAGATGTGGAAGANAATATCAAGCACTGGAAGCANAGAGANNTGTGCCCTGAAGTGACNATTCCATGTTTTACAGTCTATGGNACCTTTNTNAACAGCGACAGACAGCTGATTTTTGACATTTACAATGTGCATATATACAATAANGANATGGACAACTTTGACGAAGGATGTATCAGCGTCCTGGAAGAAACAGCAGAAAGGCANATGCTTTGGGAGCTTGATCTGATGAATTCACTCTGTTCTGACGAAAAAAGAGATGCTAGAACCGCAAGACTACTTTTGGGCTGCCCAAACGTGAGAAAAGCTGCGAATAAAGAAGGGAAAAAGCTGTTAAAGTTAAACAGCGATACATCCACTGACACACAAAGCGTTGCTTCTGAAGTGTCAGACAGGAGATCCTNTAGCTCAAGCAAGAGTAGAATTCGTAGTATTTTTGGAAGATACAATTCTCANAAAAAACCATTTGAACTAAGGTCAGGCCTCGAAGTCTTCAATGACCCTTTCAATGATTATCAGCAAGCAATAACAGATATTTGTCAATTTTCTGAGTACACACCAAACAAAGAAAGCATTCTGAAGGATTGCCTTCAAATCATACGGAAAAACCCCAGCCACACAATGGGCTCTTTTGAGTTGATCCAAGCAGTCTCAGAGTTTGGCATGAGTAAGTTTCCTCCCGAGAATATAGACAAGGCAAGGAGGGATCCAAAGAACTGGGTCAGCATCTCTGAAGTAACAGAGACAACAAGTATAGTCGCATCGCCTAAAACTCACATGATGCTAAAGGACTGCTTTAAAATCATACTGGGCACTGAGAATAAAAAAATAGTTAAAATGCTTCGAGGGAAGCTAAAGAAACTTGGTGCTATCACTACAAACATAGAGATCGGAAAAAGGGATTGCCTNGATCTACTCAGCACGGTTGACGGTCTAACAGATCAACAAAAAGAAAACATCGTGAATGGGATTTTCGAACCTTCAAAGCTGTCCTTCTACCATTGGAAAGAATTAGTCAAGAAAAGCATAGATGAGGTTCTGCTTACTGAGGATGGAAATCTAATCTTCTGCTGGTTAAAAACAATCTCATCCTCAGTTAAAGGAAGCTTGAAGAAAAGACTCAAGTTTATGAATATACATGCTCCAGAACTGATGCCAGAAAACTGTCTCTTTTCCAGCGAGGAGTTTAATGAGTTGATTAAGTTGAAGAAACTTCTCCTCAACGAACAACAAGATGAACAGGAGTTGAAGCAAGATCTTTTAATATCTTCTTGGATTAAGTGTATAATGGCTTGTAAGGACTTTGCTAGTATCAATGACAAGGTTCAAAAATTTATTTATCATCTGTCTGAAGAGCTATATAACATAAGGCTGCAACATCTGGAACTATCAAAGCTTAAGCAGGAGCATCCAAGTGTCAGCTTCACTAAGGAGGAGGTTTTAATAAAGCGGCTGGAGAAGAATTTCCTTAAGCAACACAATCTAGAAATTATGGAAACAGTAAACCTTATATTCTTTGCTGCACTTTCAGCTCCTTGGTGTCTACACTATAAAGCACTAGAATCTTATTTGGTAAGACATCCAGAGATACTCGACTGTGGTTCTAAGGAGGATTGTAGGCTCACTCTACTTGATCTGTCAGTTTCTAAACTATTAGTTTGTTTGTATCGAAAAGATGATGAGGAACTAACAAACAGCTCAAGTTTGAAACTNGGGTTNTTAGTGAANTATGCTNTCACCNTATTTACATCNAATGGNGAGCCTTTCTCACTTAGTCTGAACGACGGGGGTTTGGACCTTGATTTACACAAAACCACTGACGAGAAGTTGCTACATCAAACAAAGATAGTTTTTGCTAAGATTGGTCTGTCCGGGAACGGTTATGACTTCATCTGGACCACTCAAATGATAGCAAATAGCAACTTTAATGTCTGCAAAAGATTGACNGGAAGGAGTACNGGGGAAAGGCTTCCNAGAAGTGTCAGGAGCAAGGTCATTTATGAAATGGTAAAACTGGTAGGAGAAACAGGCATGGCAATATTGCAACAGTTAGCTTTTGCACAGGCACTAAATTATGAACACCGNTTTTATGCAGTTTTAGCACCTAAAGCACAGCTAGGAGGAGCAAGAGATCTGTTAGTGCAGGAAACTGGCACTAAAGTCATGCATGCAACTACTGAAATGTTCAGTAGAAACCTCTTAAAGACAACATCAGANGACGGCCTTACAAACCCACATCTTAAAGAGNCAATCCTTAATGTGGGATTGGACTGTCTTACCAATATGCGAAACCTTGACGGAAAGCCCATAAGTGAAGGTAGCAACTTGGTTAACTTTTACAAGGTCATNTGTATTTCGGGTGACAATACCAAGTGGGGCCCAATACACTGCTGTTCATTCTTTTCAGGTATGATGCAGCAGGTTCTTAAAAATGTTCCAGATTGGTGTTCATTCTATAAACTAACATTTATTAAGAACTTGTGTAGGCAAGTAGAGATACCAGCAGGCAGTATTAAAAAGATCTTAAATGTTCTTAGATACAAACTNTGCAGCAAAGGAGGTGTAGAGCAGCACAGTGAAGAGGANNTNAGNAAGTTNNTGNTAGACAANTTGGACAGCTGGGATGGNAACGACACAGTNAAGTTNTTAGTCACAACNTATATAAGCAAGGGGCTCATGGCACTAAACAGNTACAACCATATGGGTCANGGCATTCACCATGCAACCTCNTCAGTGTTAACTTCTTTNGCTGCNGTNCTTTTCGANGANCTAGCANTNTTTTATCTNAAGAGNAGCTTACCNCAGACAACAGTACATGTTGAGCATGCNGGCAGNTCTGATGATTANGCAAAGTGTATAGTAGTAACTGGCATACTATCCAAAGAGCTTTACTCCCAGTATGATGAGACATTTTGGAAGCATGCCTGTAGACTTAAGAATTTCACAGCTGCTGTNCAAAGGTGTTGTCAAATGAAAGATAGTGCTAAAACCNTAGTTAGCGACTGTTTTCTTGAGTTTTACAGCGAGTTCATGATGGGCTACAGAGTGACNCCTGCTGTAATTAAATTNATGTTNACTGGACTGATAAATAGCTCTGTAACTTCTCCTCAGAGCTTGATGCAGGCATGCCAAGTTTCATCTCAACAGGCCATGTATAATAGTGTTCCCCTTGTNACCAACACCACCTTTACCTTACTNAGGCAACAGATTTTCTTTAATCATGTTGAAGACTTTATCAGAAGGTATGGCNTATTAACTCTNGGAACCTTATCTCCCTTTGGNAGGCTNTTTGTNCCGACCTANTCTGGATTNGTNAGCTCAGCGGTTGCTCTGGAAGATGCTGAAGTCATTGCNAGNGCAGCTCAAACACTTCATATGAACAGTGTGTCNATCCAGTCAAGTAGCTTGACTACATTAGACAGTTTAGGTCGTAGCAGGACAAGTTCCATAGTTGAAGATAGCAGCAGTGTAAGCGACACTACTGTTGCTTCTCATGATTCGGGATCATCATCATCAAGCTTCTCTTTTGAGCTCAATAGGCCTCTATCTGAAACTGAACTACAATTCATCAAAGCACTAAACAGCCTCAAATCAACCCAAGCTTGTGAGATAATTCAGAACAGGATTACAGGTCTTTATTGTAATAGCAATGAAGGACCCCTCGACAGACACAATGTTATTTACAGTAGCAGAATGGCAGATTCTTGTGACTGGCTAAAAGATGGTAAGAGAAGAGGGAATCTAGAACTNGCAAANAGAATCCANTCTGTACTNTGTGTTNTNATAGCNGGNTACTACAGATCATTTGGNGGGGAAGGGACTGANAAACAGGTAAANGCATCATTNAATAGGGANGACAATAAAATCATCGAAGATCCTATGATACAANTGATTCCGGAGAAACTGAGGAGNGAGTTGGANAGGTTAGGGGTTTCTAGAATGGAAGTCGATGANCTGATGCCAAGNATTAGCCCTGATGANACNTTAGCCCAACTTGTGGCAAAAAAACTAATNAGCCTCAATGTTTCGACAGAAGAATACTCNGCAGAGGTNTCTAGGCTCAANCAAACNCTAACNGCNCGNAATGTTTTGCACGGGTTNGCTGGAGGAATAAAAGANCTCTCGCTTCCTATATATACAATATTCATGAAGTCATACTTCTTCAAAGACAANGTNTTNNTGTCACTGACAGACAGNTGGTCNACCAAGCANAGCACGAACTACCGTGACAGCTGCGGTAAACAGTTGACTGGTAGGATAATCACNAAGTACACTCACTGGTTGGACACTTTNCTAAGCTGCTCTGTNTCCATTAANAGGCANACAACTGTNAAGGAGCCTTCCCTTTTTAATCCGAACATCAGGTGTGTCAACCTGATCACATTTGAAGACGGTTTGAGGGAACTTTCAGTGATACAGAGTCATCTCAAAGTTTTTGAGAACGAATTCACTAACTTAAACCTTCAGTTCTCTGACCCAAACAGACAGAAACTTAGGATAGTTGAATCTAGACCTGCAGAATCTGAGTTAGAGGCAAATCGTGCAGTGATTGTTAAGACTAAACTGTTTTCAGCAACCGAACAGGTCCGANTATCTAANAACCCTGCAGTTGTCATGGGTTATCTATTAGACGAGTCAGCAATTTCTGAAGTTAAACCTACCAAGGTTGATTTTTCGAATTTACTTAAAGATCGCTTCAAAATAATGCAATTTTTCCCTTCTGTGTTCACTTTGATCAAAATGCTAACAGATGAGTCGTCAGACTCAGAAAAGAATGGCCTTAGCCCAGATTTGCAACAAGTTGCAAGGTATTCTAACCATTTAACCTTGCTTAGTAGAATGATACAACAAGCAAAGCCAACTGTAACTGTTTTCTACATGCTAAAGGGTAACTTAATGAACACAGAACCGACAGTCGCTGAGCTTGTCAGTTACGGTATAAAGGAAGGTAGGTTCTATAGGCTTTCTGACACCGGAATTGATGCAAGTACATATTCTGTAAAATACTGGAAAATTCTCCACTGTATTTCTGCTATCGGATGCCTACCTCTGAGTCAAGCAGATAAGTCTTCACTACTCATGAGTTTCTTAAATTGGAGGGTGAACATGGACATTAGAACTTCTGACTGTCCATTGTCTAGCCATGAGGCAAGTATACTTAGTGAATTTGACGGACAAGTTATTGCTAATATACTTGCCAGTGAATTAAGTTCTGTAAAACGAGACTCTGAACGNGAAGGTCTAACTGATCTCCTTGATTACCTAAANTCACCNACTGAACTGTTNAAGAAGAAGCCNTANTTAGGAACAACCTGCAAGTTCANCACTTGGGGAGACTCAAANAGNTCTGGTAAGTTTACATACAGTAGCAGATCTGGNGAGTCAATTGGTATCTTCATTGCAGGGAAATTGCACATCCATCTTTCATCTGAGTCTGTTGCCCTNTTGTGTGAGACTGAAAGGCAAGTGCTCTCTTGGATGAGCAAAAGGAGGACTGAGGTGATAACTAAGGAACAACATCAATTGTTCCTGAGCCTCCTTCCACAATCTCATGAATGNTTACAAAAGCACAANGATGGCAGTGCACTGTCAGTNATACCTGATNGNAGCAANCCTCGNCTACTAAAATTTGTGCCTCTCAAGAAGGGGCTNGCAGTGGTGAAGATNAAAAAACAAATTTTGACAGTNAAGAANCAAGTNGTGTTTGATGCTGAAAGCGAGCCCAGNTTNCAATGGGGGCATGGCTGCTTGTCCATTGTTTATGACGAAACCGACACTCAGACCACATACCATGAAAACCTTTTGAAGGTGAAGCAGCTTGTTGACTGCTCTACCGACAGAAAGAAGCTTTTACCTCAGTCTGTGTTTTCTGATTCCAAAGTCGTCCTCTCAAGAATTAAGTTTAAAACGGAACTCCTTCTTAACTCATTGACGTTGCTCCACTGTTTCTTGAAACATGCCCCTAGTGATGCTATAATGGAAGTGGAGAGTAAAAGTAACCTACTACATAAGTACCTCAAATCAGGAGGTGTTAGGCAGCGGAATACTGAGGTNCTCTTNAGNGAAAAGTTGAANAAGGTNGTTATAAAGGANAACCTTGAGCAAGGNGTGGAAGAAGAGATTGANTTNTGCAACAACCTNACCAAGANTGTTTCNGAGAATCCGCTACCACTCAGCTGTTGGTCTGAAGTTCAAAGCTATATTGAAGACATAGGCTTCAACAATGTGCTTGTGAATATTGACAGAAACACTGTTAAAAGTGAACTTTTGTGGAAATTTACGTTAGACACCAATGTAAGTACCACAAGTACCATCAAGGATGTGAGGACACTGGTATCCTACGTTAGCACTGAAACGATCCCTAAATTTCTGCTTGCATTTCTTCTTTATGAAGAAGTGTTGATGAACTTAATTAACCAGTGCAAGGCAGTAAAGGAACTCATCAACAGCACAGGACTCTCAGATCTAGAATTAGAGAGCTTGCTCACTTTGTGTGCTTTTTATTTCCAAAATGAGTGCAGTAAGAGAGATGGACCTAGGTGTTCNTTCGCAGCACTGTTAAGCTTAGTTCATGAAGATTGGCAAANGNTAGGNAAAAACATCCTTGTTCGTGCAAACAATGAGCTGGGTGANGTGTCNCTNAAGGTNAANATTGTCCTGGTGCCNCTCAANGACATGTCCAAGCCNAANCCTGAGAGAGTNGTTATAGCCAGAAGGTCACTGAATCANGCTCTNTCCTTAATGTTTTTGGATGAAATGTCATTACCTGAGCTTAAATCCTTATCTGTTAATTGCAGAATGGGNAACTTTGAAGGGCAGGAGTGCTTTGAGTTCACNATTTTNAANGACAACAGCNCAAGGCTGGATTACAACAAANTAATTGACCACTGTGTGGACATGGAAAAAAAGAGGGACGCAGTTAGAGCAGTAGAAGATTTAGTTNTGATGTTGACAGGCAGGGCAGTCAAACCTAGCACTGTAACACCAGNTGCACANGAAGANGAGCAGTGTCAGGAGCAAATAAGNCTNGATGATCTAATGGCAAGTGACACAGTGACAGACCTNCCNGANAGGGAAGCAGAGGCCCTNAAAACAGGNAANCTTGGCTTTAACTGGGATTCAGATTGANCACNNTNTCTGTNTNAATNATTNATACCTNTCANTNTCNNNAGGGNAAGTAAGGCAATTTATACCATGCCATTTGTTGACATCTGAACTTTCAAATAAGTCAGCTGCTCTGCATCTCTTACCAATTCAATTGTTTCACTACAATGTTTTCAGCTACTGGTCAACCTTTAATATCCAACTACTCCACTCTCTTTGCTGCTCATGTC >test_NIHPAK-19_M GTGGATTGAGCATCTTAATTGCAGCATACTTGTCAACATCATGCATATATCATTGATGTATGCAGTTTTCTGCTTGCAGCTGTGCGGTCTAGGGAAAACTAACGGACTACACAATGGGACTGAACACAATAAGACACACGTTATGACAACGCCTGATGACAGTCAGAGCCCTGAACCGCCAGTGAGCACAGCCCTGCCTGTCACACCGGACCCTTCCACTGTCACACCTACAACACCAGCCAGCGGATTAGAAGGCTCAGGAGAGGTTCACACATCCTCTCCAATCACCACCAAGGGTTTGTCTCTGCCGGGGGCTACATCTGAGCTCCCTGCGACTACTAGCATAGTCACTTCAGGTGCAAGTGATGCCGATTCTAGCACACAGGCAGCCAGAGACACCCCTAAACCATCAGTCCGCACGAGTCTGCCCAACAGCCCTAGCACACCATCCACACCACAAGGCACACACCATCCCGTGAGGAGTCTGCTTTCAGTCACGAGCCCTAAGCCAGAAGAAACACCAACACCGTCAAAATCAAGCAAAGATAGCTCAGCAACCAACAGTCCTCACCCAGCCGCCAGCAGACCAACAACCCCTCCCACAACAGCCCAGAGACCCGCTGAAAACAACAGCCACAACACCACCGAACAGCTTGAGTCCTTAACACAATTAGCAACTTCAGGTTCAATGATCTCTCCAACACAGACAGTCCTCCCAAAGAGTGTTACTTCTATAGCCATTCAAGACATTCATCCCAGCCCAACAAATAGGTCTAAAAGAAACCTTGATATGGAAATAATCTTGACGTTATCTCAGGGTCTGAAAAAGTATTATGGCAAAATACTTAAGCTCCTGCATCTCACCTTAGAGGAAGACACTGAAGGCTTGTTAGAATGGTGCAAGAGAAATCTCGGTCTTGACTGTGATGACACCTTCTTTCAAAAAAGAATTGAAGAATTCTTTATAACTGGTGAGGGTCATTTCAATGAAGTTTTACAATTTAGAACACTAGGCACATTGAGCACTACAGAGTCAACGCATGCTGGATCACCAACAGTTGAACCCTTCAAATCCTACTTTGCTAAAGGTTTCCTTTCAATAGATTCAGGTTATTTCTCTGCCAAATGTTATTCAAGAACATCCAATTCAGGGCTCCAATTGATTAATGTTACCCGACATTCATCTAGGATAGCTGACACGCCTGGGCCCAAGATCACTAACCTAAAGACCATCAATTGCATAAACTTAAAAGCATCCGTCTTTAAAGAACATAGAGAGGTTGAAATCAATGTGCTTCTCCCTCAAGTTGCAGTCAACCTCTCAAACTGTCATGTTGCAATCAAATCACATGTCTGCGACTATTCTTTGGACACTGACGGGGCGATTAGGCTTCCTCATATTCATCATGAAGGTACTTTTATCCCAGGTACTTACAAAATAGTGATAGACCAAAAAAGTAAGCTGAATGACAGGTGCACCCTATTCACCAACTGTGTGATAAAAGGAAGAGAAGTTCGTAAAGGCCAGTCAGTCCTAAGGCAATATAAGACAGAAATTAGAATTGGCAGGGCATCAACTGGTTCTAGGAGATTGCTTTCCGAAGAATCTGGTGATGACTGCATATCAAGAACTCAGCTATTGAGGACAGAGACTGCAGAGGTCCATGGCGATAACNNNNNNNNNNCAGGTGATAAGATAACCATCTGTAATGGTTCAACTGTTGTAGATCAAAGACTGGGTAGTGAACTGGGGTGTTACACTATCAATAGAGTGAGGTCATTCAAGCTATGCGAAAACAGTGCCACAGGGAAGAGCTGTGAAATAGACAGTATCCCAGTTAAGTGTAGGCAGGGTTATTGCCTAAAAATCACTCAGGAAGGGAGGGGCCATGTGAAATTATCTAGAGGCTCAGAAGTTGTCTTGGATGTATGTGACTCAAGCTGTGAAGTGATGATACCTAAGGGCACTGGTGACATTCTAGTAGATTGTTCAGGTGGGCAGCAACATTTTTTAAAAGACAACCTGGTTGATCTAGGATGTCCCAAAATTCCATTATTGGGCAAAATGGCTATTTATATCTGCAGAATGTCGAATCACCCCAAAACAACCATGGCCTTCCTCTTTTGGTTCAGCTTTGGCTATGTGGTAACTTGTATACTTTGCAAGGCCATTTTTTTCTTATTAATAATTTTTGGAACACTAGGGAAAAGGTTCAAGCAGTACAGAGAGCTGAAACCCCAGACCTGCACCATTTGTGAGACAACACCTGTAAATGCAATAGATGCTGAAATGCATGATCTCAACTGCAGTTACAATATATGTCCCTATTGTGCGTCTAGACTGACTTCAGATGGGCTTGCTAGGCATGTAACACAATGCCCTAGACGGAAGGAGAAAGTGGAGGAAACCGAATTGTACCTGAATTTAGAGAGAATTCCTTGGGTTGTAAGAAAGCTATTACAGGTGTCAGAGTCCACTGGTACAGTATTAAAAAGGAGCAGTTGGCTAATTGTTCTACTTGTGCTGTTCACAGTTTCATTATCACCAGTTCAATCAGCACCCATTGGTCACGGGAGAACAATTGAAACATACCGGGTTAGGGAGGAATACACAAGTATTTGCCTCTTTGTACTAGGAAGTATCCTGTTTATGGTTTCTTGTCTAATGAAAGGACTAGTTGACAGTGTTGGCAACATCTTCTTTCCTGGGCTGTCCGTTTGTAAGACATGCTCTATAGGTAGCATTAATGGCTTTGAAATTGAGTCTCATAAGTGCTACTGTAGCTTGTTTTGTTGCCCTTATTGTAGGCACTGCTCTGCTGATAGAGAGATTCATCAGCTGCACTTGAGCATCTGCAAAAAAAGGAAGACAGGAAGTAATGTTATGCTAGCTGTTTGCAAACGCATGTGTTTCAGGGCAACTATGGAAGTGAGCAACAAAGCCCTATTTATCCGTAGCATTATCAACACCACTTTTGTTGTGTGCATACTGATACTAGCAGTCTGTGTTGTTAGCACCTCAGCAGTAGAAATGGAAAGCCTGCCAGCTGGGACCTGGGAAAGAGAAGAAGACCTAACAAATTTCTGCCATCAGGAATGCCAGGTCACGGAGACTGAGTGCCTCTGCCCTTATGAAGCTCTAGTGCTCAGAAGGCCCCTATTTCTAGATAGTATAGTCAAAGGCATGAAAAATCTGCTAAACTCAACAAGTCTAGAAACAAGCTTATCAATTGAAGCACCGTGGGGAGCAATTAATGTTCAGTCAACCTACAAACCAACTGTATCAACTGCAAACATAGCACTTAGTTGGAGCTCAGTGGAACACAGAGGCAATAAGGTTTTGGTCTCAGGCAGATCAGAATCAATTATGAAGCTGGAAGAAAGGACAGGAATCAGCTGGGATCTTGGCGTAGAAGATGCCTCTGAGTCTAAGCTACTTACAGTTTCAGTCATGGATTTGTCTCAGATGTACTCTCCTGTCTTCGAGTACTTATCAGGTGACAGACAAGTGGAAGAGTGGCCTAAAGCAACCTGTACAGGTGACTGCCCAGAAAGATGTGGCTGCACATCATCAACCTGCTTACACAAAGAGTGGCCCCATTCAAGGAATTGGAGATGTAATCCTACTTGGTGCTGGGGTGTGGGGACTGGCTGCACCTGTTGTGGTTTAGATGTGAAAGACCTTTTCACAGATTACATGTTCGTCAAGTGGAAAGTTGAGTACATTAAGACAGAGGCCATAGTATGTGTGGAACTAACCAGTCAAGAAAGACAGTGTAGCTTGATTGAGGCGGGCACAAGATTCAATTTAGGTTCTGTGACTATTACATTGTCAGAACCAAGGAACATTCAACAAAAGCTCCCTCCTGAAATAATCACACTGCACCCCAAGATTGAGGAAGGTTTTTTTGACCTAATGCATATACAAAAAGTGCTATCGGCAAGCACAGTGTGTAAGTTGCAGAGTTGCACACATGGTGTGCCAGGAGATCTGCAGGTCTACCACATCGGAAACCTATTAAAAGGGGACAGAGTAAATGGACACCTGATTCACAAAATTGAGCAACACCTCAACACCTCCTGGATGTCCTGGGATGGTTGCGACCTAGACTACTACTGTAACATGGGAGACTGGCCTTCCTGCACATATACCGGAGTCACTCAGCACAATCATGCTTCATTTGTAAACCTGCTCAACATTGAAACTGATTATACAAAAACCTTCCACTTTCACTCTAAAAGGGTTACTGCACATGGAGACACACCACAACTAGATCTGAAGGCAAGGCCAACCTATGGTGCAGGTGAGATCACCGTGCTGGTGGAAGTTGCTGACATGGAGTTACACACAAAGAAGATTGAAATATCAGGCTTAAAATTTGCAAGCCTAACTTGCACAGGTTGTTATGCTTGTAGTTCTGGCATCTCCTGTAAAGTTAGAATTCATGTGGATGAACCAGATGAACTTACAGTACATGTTAAAAGTGATGACCCAGATGTAGTTGCAGCTAGCTCAAGTCTCATGGCAAGGAAGCTTGAATTTGGAACAGACAGTACATTTAAAGCTTTCTCAGCCATGCCAAAAACTTCCCTATGTTTCTACATTGTGGAAAGAGAATACTGTAAGAGCTGCAGTAAAGAAGACACACAGAAATGTGTTAACACGAAACTCGAACAACCACAGAGCATTTTGATCGAACATAAGGGAACTATAATTGGAAAGCAAAACAATACTTGCACGGCTAAAGCGAGCTGCTGGTTAGAGTCAGTTAAGAGTTTTTTTTATGGTCTGAAGAATATGCTTGGTGGCATTTTTGGCAATGTTTTTATAGGCATTTTCACATTTCTTGCCCCCTTTATCNTNTTAATACTTTTCTTTATGTTTGGGTGGAGGGTCTTGTTTTGCTTCAAGTGTTGCAGAAGAACCAGAGGCCTATTCAAGTACAGGCACCTCAAAGACGATGAAGAAACTGGTTACAGAAAGATCATTGAAAGACTGAACAACAAAAAAGGAAAAAACAAGCTGCTTGATGGTGAAAGACTTGCTGACAGAAAGATTGCTGAACTGTTCTCTACAAAAACACACATTGGCTAGATCAACCGGAGGGGCCTGGGNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNTGCATCCCCACCATATTATCATCACAATATGCCACATCTAAGCTGATTCACTGTATCTGCAAACAGACTCTGTAATGCTTGAAACTGCCT diff --git a/website/src/components/Edit/SequencesForm.spec.tsx b/website/src/components/Edit/SequencesForm.spec.tsx index 3428287115..9633e392b0 100644 --- a/website/src/components/Edit/SequencesForm.spec.tsx +++ b/website/src/components/Edit/SequencesForm.spec.tsx @@ -29,7 +29,6 @@ function makeSubOrganismReferenceSchema(suborganisms: string[]): ReferenceGenome return result; } -/* eslint-disable @typescript-eslint/naming-convention -- this test has keys that expectedly contain spaces */ describe('SequencesForm', () => { beforeEach(() => { vi.spyOn(toast, 'error'); @@ -48,6 +47,7 @@ describe('SequencesForm', () => { }); test('GIVEN organism with 2 suborganisms with 1 segment each THEN allows at max 1 inputs', async () => { + const FASTAHEADER = 'FASTAHEADER'; let editableSequences = EditableSequences.fromSequenceNames( makeSubOrganismReferenceSchema(['suborg1', 'suborg2']), ); @@ -57,21 +57,22 @@ describe('SequencesForm', () => { ]); const firstKey = initialRows[0].key; { - editableSequences = editableSequences.update(firstKey, 'ATCG', 'Segment 1', 'subId_Segment1'); + editableSequences = editableSequences.update(firstKey, 'ATCG', 'Segment 1', FASTAHEADER); const fasta = editableSequences.getSequenceFasta(); expect(fasta).not.toBeUndefined(); const fastaText = await fasta!.text(); - expect.soft(fastaText).toBe('>subId_Segment1\nATCG'); - expect(editableSequences.getSequenceRecord()).deep.equals({ subId_Segment1: 'ATCG' }); + expect.soft(fastaText).toBe(`>${FASTAHEADER}\nATCG`); + expect(editableSequences.getSequenceRecord()).deep.equals({ [FASTAHEADER]: 'ATCG' }); const rows = editableSequences.rows; expect(rows).toEqual([ - { label: 'Segment 1', value: 'ATCG', initialValue: null, key: firstKey, fastaHeader: 'subId_Segment1' }, + { label: 'Segment 1', value: 'ATCG', initialValue: null, key: firstKey, fastaHeader: FASTAHEADER }, ]); } - expect(() => editableSequences.update('another key', 'GG', 'another key', 'subId_anotherkey')).toThrowError( - 'Maximum limit reached — you can add up to 1 sequence file(s) only.', - ); + + expect(() => + editableSequences.update('another key', 'GG', 'another key', 'FASTAHEADER_anotherkey'), + ).toThrowError('Maximum limit reached — you can add up to 1 sequence file(s) only.'); editableSequences = editableSequences.update(firstKey, null, null, null); expect(editableSequences.rows).toEqual([ { label: 'Add a segment', value: null, fastaHeader: null, initialValue: null, key: expect.any(String) }, @@ -79,12 +80,12 @@ describe('SequencesForm', () => { const rowsAfterDeletion = editableSequences.rows; const newFirstKey = rowsAfterDeletion[0].key; { - editableSequences = editableSequences.update(newFirstKey, 'ATCG', 'Segment 1', 'subId_Segment1'); + editableSequences = editableSequences.update(newFirstKey, 'ATCG', 'Segment 1', FASTAHEADER); const fasta = editableSequences.getSequenceFasta(); expect(fasta).not.toBeUndefined(); const fastaText = await fasta!.text(); - expect.soft(fastaText).toBe('>subId_Segment1\nATCG'); - expect(editableSequences.getSequenceRecord()).deep.equals({ subId_Segment1: 'ATCG' }); + expect.soft(fastaText).toBe(`>${FASTAHEADER}\nATCG`); + expect(editableSequences.getSequenceRecord()).deep.equals({ [FASTAHEADER]: 'ATCG' }); const rows = editableSequences.rows; expect(rows).toEqual([ @@ -93,13 +94,17 @@ describe('SequencesForm', () => { value: 'ATCG', initialValue: null, key: newFirstKey, - fastaHeader: 'subId_Segment1', + fastaHeader: FASTAHEADER, }, ]); + expect(editableSequences.getFastaIds()).toEqual(FASTAHEADER); } }); test('GIVEN organism with 2 segments THEN allows at max 2 inputs', async () => { + const FASTAHEADER_SEGMENT1 = 'FASTAHEADER'; + const FASTAHEADER_WITH_DESCRIPTION = FASTAHEADER_SEGMENT1 + ' description'; + const FASTAHEADER_SEGMENT2 = 'FASTAHEADER_SEGMENT2'; let editableSequences = EditableSequences.fromSequenceNames( makeReferenceGenomeLightweightSchema(['foo', 'bar']), ); @@ -112,42 +117,65 @@ describe('SequencesForm', () => { let secondKey; { - editableSequences = editableSequences.update(firstKey, 'ATCG', 'Segment 1', 'subId_Segment1'); + editableSequences = editableSequences.update(firstKey, 'ATCG', 'Segment 1', FASTAHEADER_WITH_DESCRIPTION); const fasta = editableSequences.getSequenceFasta(); expect(fasta).not.toBeUndefined(); const fastaText = await fasta!.text(); - expect.soft(fastaText).toBe('>subId_Segment1\nATCG'); - expect(editableSequences.getSequenceRecord()).deep.equals({ subId_Segment1: 'ATCG' }); + expect.soft(fastaText).toBe(`>${FASTAHEADER_WITH_DESCRIPTION}\nATCG`); + expect(editableSequences.getSequenceRecord()).deep.equals({ [FASTAHEADER_WITH_DESCRIPTION]: 'ATCG' }); const rows = editableSequences.rows; expect(rows).toEqual([ - { label: 'Segment 1', value: 'ATCG', initialValue: null, fastaHeader: 'subId_Segment1', key: firstKey }, + { + label: 'Segment 1', + value: 'ATCG', + initialValue: null, + fastaHeader: FASTAHEADER_WITH_DESCRIPTION, + key: firstKey, + }, { label: 'Add a segment', value: null, initialValue: null, fastaHeader: null, key: expect.any(String) }, ]); secondKey = rows[1].key; } { - editableSequences = editableSequences.update(secondKey, 'TT', 'Segment 2', 'subId_Segment2'); + editableSequences = editableSequences.update(secondKey, 'TT', 'Segment 2', FASTAHEADER_SEGMENT2); const fasta = editableSequences.getSequenceFasta(); expect(fasta).not.toBeUndefined(); const fastaText = await fasta!.text(); - expect.soft(fastaText).toBe('>subId_Segment1\nATCG\n>subId_Segment2\nTT'); - expect(editableSequences.getSequenceRecord()).deep.equals({ subId_Segment1: 'ATCG', subId_Segment2: 'TT' }); + expect.soft(fastaText).toBe(`>${FASTAHEADER_WITH_DESCRIPTION}\nATCG\n>${FASTAHEADER_SEGMENT2}\nTT`); + expect(editableSequences.getSequenceRecord()).deep.equals({ + [FASTAHEADER_WITH_DESCRIPTION]: 'ATCG', + [FASTAHEADER_SEGMENT2]: 'TT', + }); const rows = editableSequences.rows; expect(rows).deep.equals([ - { label: 'Segment 1', value: 'ATCG', initialValue: null, key: firstKey, fastaHeader: 'subId_Segment1' }, - { label: 'Segment 2', value: 'TT', initialValue: null, key: secondKey, fastaHeader: 'subId_Segment2' }, + { + label: 'Segment 1', + value: 'ATCG', + initialValue: null, + key: firstKey, + fastaHeader: FASTAHEADER_WITH_DESCRIPTION, + }, + { + label: 'Segment 2', + value: 'TT', + initialValue: null, + key: secondKey, + fastaHeader: FASTAHEADER_SEGMENT2, + }, ]); } expect(() => editableSequences.update('another key', 'GG', 'another key', 'anything')).toThrowError( 'Maximum limit reached — you can add up to 2 sequence file(s) only.', ); + expect(editableSequences.getFastaIds()).toEqual(`${FASTAHEADER_SEGMENT1} ${FASTAHEADER_SEGMENT2}`); }); test('GIVEN a multi-segmented organism THEN do not allow duplicate fasta headers', () => { + const FASTAHEADER = 'FASTAHEADER'; let editableSequences = EditableSequences.fromSequenceNames( makeReferenceGenomeLightweightSchema(['foo', 'bar']), ); @@ -158,23 +186,50 @@ describe('SequencesForm', () => { ]); const firstKey = initialRows[0].key; - editableSequences = editableSequences.update(firstKey, 'ATCG', 'Segment 1', 'subId_Segment'); + editableSequences = editableSequences.update(firstKey, 'ATCG', 'Segment 1', FASTAHEADER); const rowsAfterFirstUpdate = editableSequences.rows; const secondKey = rowsAfterFirstUpdate[1].key; - editableSequences = editableSequences.update(secondKey, 'TT', 'Segment 2', 'subId_Segment'); + editableSequences = editableSequences.update(secondKey, 'TT', 'Segment 2', 'FASTAHEADER description'); - const errorMessage = 'A sequence with the fastaHeader subId_Segment already exists.'; + const errorMessage = `A sequence with the fastaID ${FASTAHEADER} already exists.`; expect(toast.error).toHaveBeenCalledWith(expect.stringContaining(errorMessage)); // Expect that the second sequence was not added expect(editableSequences.rows).toEqual([ - { label: 'Segment 1', value: 'ATCG', initialValue: null, key: firstKey, fastaHeader: 'subId_Segment' }, + { label: 'Segment 1', value: 'ATCG', initialValue: null, key: firstKey, fastaHeader: FASTAHEADER }, + { label: 'Add a segment', value: null, initialValue: null, fastaHeader: null, key: expect.any(String) }, + ]); + expect(editableSequences.getFastaIds()).toEqual(FASTAHEADER); + }); + + test('GIVEN a single-segmented organism THEN only allows 1 input', async () => { + const FASTAHEADER = 'FASTAHEADER'; + let editableSequences = EditableSequences.fromSequenceNames(makeReferenceGenomeLightweightSchema(['foo'])); + + const initialRows = editableSequences.rows; + expect(initialRows).toEqual([ { label: 'Add a segment', value: null, initialValue: null, fastaHeader: null, key: expect.any(String) }, ]); + const key = initialRows[0].key; + + editableSequences = editableSequences.update(key, 'ATCG', key, FASTAHEADER); + const fasta = editableSequences.getSequenceFasta(); + expect(fasta).not.toBeUndefined(); + const fastaText = await fasta!.text(); + expect.soft(fastaText).toBe(`>${FASTAHEADER}\nATCG`); + + expect(editableSequences.getSequenceRecord()).deep.equals({ [FASTAHEADER]: 'ATCG' }); + + const rows = editableSequences.rows; + expect(rows).deep.equals([{ label: key, value: 'ATCG', initialValue: null, fastaHeader: FASTAHEADER, key }]); + + expect(() => editableSequences.update('another key', 'GG', 'another key', 'anything')).toThrowError( + 'Maximum limit reached — you can add up to 1 sequence file(s) only.', + ); }); - test('GIVEN a single-segmented organism THEN only allows 1 input and fasta header does not contain the segment name', async () => { + test('GIVEN an edit with an empty fasta header THEN use key as fasta header', async () => { let editableSequences = EditableSequences.fromSequenceNames(makeReferenceGenomeLightweightSchema(['foo'])); const initialRows = editableSequences.rows; @@ -183,16 +238,16 @@ describe('SequencesForm', () => { ]); const key = initialRows[0].key; - editableSequences = editableSequences.update(key, 'ATCG', key, 'subId'); + editableSequences = editableSequences.update(key, 'ATCG', 'subId', null); const fasta = editableSequences.getSequenceFasta(); expect(fasta).not.toBeUndefined(); const fastaText = await fasta!.text(); - expect.soft(fastaText).toBe('>subId\nATCG'); + expect.soft(fastaText).toBe(`>${key}\nATCG`); - expect(editableSequences.getSequenceRecord()).deep.equals({ subId: 'ATCG' }); + expect(editableSequences.getSequenceRecord()).deep.equals({ [key]: 'ATCG' }); const rows = editableSequences.rows; - expect(rows).deep.equals([{ label: key, value: 'ATCG', initialValue: null, fastaHeader: 'subId', key }]); + expect(rows).deep.equals([{ label: 'subId', value: 'ATCG', initialValue: null, fastaHeader: key, key }]); expect(() => editableSequences.update('another key', 'GG', 'another key', 'anything')).toThrowError( 'Maximum limit reached — you can add up to 1 sequence file(s) only.', @@ -216,20 +271,23 @@ describe('SequencesForm', () => { expect(editableSequences.rows).toEqual([ { label: 'Add a segment', value: null, initialValue: null, fastaHeader: null, key: expect.any(String) }, ]); + expect(editableSequences.getFastaIds()).toEqual(''); }); test('GIVEN initial data with an empty segment THEN the fasta does not contain the empty segment', async () => { + const FASTAHEADER = 'FASTAHEADER_label'; let editableSequences = EditableSequences.fromInitialData( defaultReviewData, makeReferenceGenomeLightweightSchema(['originalSequenceName', 'anotherSequenceName']), ); - editableSequences = editableSequences.update(editableSequences.rows[0].key, 'ATCG', 'label', 'subId_label'); + editableSequences = editableSequences.update(editableSequences.rows[0].key, 'ATCG', 'label', FASTAHEADER); const fasta = editableSequences.getSequenceFasta(); expect(fasta).not.toBeUndefined(); const fastaText = await fasta!.text(); - expect.soft(fastaText).toBe('>subId_label\nATCG'); + expect.soft(fastaText).toBe(`>${FASTAHEADER}\nATCG`); - expect(editableSequences.getSequenceRecord()).deep.equals({ subId_label: 'ATCG' }); + expect(editableSequences.getSequenceRecord()).deep.equals({ [FASTAHEADER]: 'ATCG' }); + expect(editableSequences.getFastaIds()).toEqual(FASTAHEADER); }); test('GIVEN initial segment data that is then deleted as an edit THEN the edit record does not contain the segment key but input field is kept', () => { @@ -264,5 +322,6 @@ describe('SequencesForm', () => { key: expect.any(String), }, ]); + expect(editableSequences.getFastaIds()).toEqual(''); }); }); diff --git a/website/src/components/Edit/SequencesForm.tsx b/website/src/components/Edit/SequencesForm.tsx index b981c4b3dc..2f5a9d63bc 100644 --- a/website/src/components/Edit/SequencesForm.tsx +++ b/website/src/components/Edit/SequencesForm.tsx @@ -22,6 +22,11 @@ function generateAndDownloadFastaFile(fastaHeader: string, sequenceData: string) URL.revokeObjectURL(url); } +function getFastaId(fastaHeader: string | null): string | null { + if (!fastaHeader) return null; + return fastaHeader.split(/\s+/)[0] ?? null; +} + type EditableSequenceFile = { key: string; label: string | null; @@ -136,8 +141,8 @@ export class EditableSequences { } fastaHeader ??= value == null ? null : key; // Ensure fastaHeader is never null if a sequence exists - if (this.editableSequenceFiles.some((seq) => seq.fastaHeader === fastaHeader)) { - toast.error(`A sequence with the fastaHeader ${fastaHeader} already exists.`); + if (this.editableSequenceFiles.some((seq) => getFastaId(seq.fastaHeader) === getFastaId(fastaHeader))) { + toast.error(`A sequence with the fastaID ${getFastaId(fastaHeader)} already exists.`); return new EditableSequences( this.editableSequenceFiles.filter((file) => file.value !== null), this.maxNumberOfRows, @@ -159,8 +164,13 @@ export class EditableSequences { } getFastaIds(): string { - const filledRows = this.rows.filter((row) => row.value !== null); - return filledRows.map((sequence) => sequence.fastaHeader).join(FASTA_IDS_SEPARATOR); + return this.rows + .flatMap((row) => { + if (row.value === null) return []; + const id = getFastaId(row.fastaHeader); + return id === null || id === '' ? [] : [id]; + }) + .join(FASTA_IDS_SEPARATOR); } getSequenceFasta(): File | undefined { diff --git a/website/src/components/Submission/FileUpload/fileProcessing.spec.ts b/website/src/components/Submission/FileUpload/fileProcessing.spec.ts index 96a5e0223a..5dc4e8784b 100644 --- a/website/src/components/Submission/FileUpload/fileProcessing.spec.ts +++ b/website/src/components/Submission/FileUpload/fileProcessing.spec.ts @@ -70,7 +70,9 @@ describe('fileProcessing', () => { }); test('Plain segment file content is extracted correctly', async () => { - const dummyFile = new File(['>fooid\nACTG\n\nACTG\nACTG\n'], 'example.fasta', { type: 'text/plain' }); + const dummyFile = new File(['>fooid description\nACTG\n\nACTG\nACTG\n'], 'example.fasta', { + type: 'text/plain', + }); const result = await PLAIN_SEGMENT_KIND.processRawFile(dummyFile); if (result.isErr()) { fail(`result was error: ${result._unsafeUnwrapErr().message}`); @@ -78,6 +80,6 @@ describe('fileProcessing', () => { const processedFile = result._unsafeUnwrap(); const processedText = await processedFile.text(); expect(processedText).toBe('ACTGACTGACTG'); - expect(processedFile.fastaHeader()).toBe('fooid'); + expect(processedFile.fastaHeader()).toBe('fooid description'); }); }); diff --git a/website/src/components/Submission/FileUpload/fileProcessing.ts b/website/src/components/Submission/FileUpload/fileProcessing.ts index b28ee15c48..f3c1000868 100644 --- a/website/src/components/Submission/FileUpload/fileProcessing.ts +++ b/website/src/components/Submission/FileUpload/fileProcessing.ts @@ -94,7 +94,11 @@ export const PLAIN_SEGMENT_KIND: FileKind = { new Error(`Found ${headerLines.length} headers in uploaded file, only a single header is allowed.`), ); } - const header = headerLines.length === 1 ? headerLines[0].substring(1).trim() : null; + let header: string | null = null; + if (headerLines.length === 1) { + const trimmed = headerLines[0].substring(1).trim(); + header = trimmed === '' ? null : trimmed; + } const segmentData = lines .filter((l) => !l.startsWith('>')) .map((l) => l.trim())