diff --git a/.ebextensions/02_data_downloads.config b/.ebextensions/02_data_downloads.config index b2b2b883..5a76d23b 100644 --- a/.ebextensions/02_data_downloads.config +++ b/.ebextensions/02_data_downloads.config @@ -6,12 +6,12 @@ commands: container_commands: 01_s3_download: - test: test ! -d "/usr/local/share/seqrepo/2024-02-20" + test: test ! -d "/usr/local/share/seqrepo/2024-12-20" command: "aws s3 cp s3://${AWS_BUCKET_NAME}/${AWS_SEQREPO_OBJECT} /usr/local/share/seqrepo.tar.gz --region us-east-2" 02_extract_seqrepo: test: test -f "/usr/local/share/seqrepo.tar.gz" - command: "mkdir -p /usr/local/share/seqrepo/2024-02-20 && tar -xzvf /usr/local/share/seqrepo.tar.gz -C /usr/local/share/seqrepo/2024-02-20" + command: "mkdir -p /usr/local/share/seqrepo/2024-12-20 && tar -xzvf /usr/local/share/seqrepo.tar.gz -C /usr/local/share/seqrepo/2024-12-20" 03_seqrepo_zip_permission: test: test -f "/usr/local/share/seqrepo.tar.gz" diff --git a/.gitignore b/.gitignore index d25a2f73..72ab0ae2 100644 --- a/.gitignore +++ b/.gitignore @@ -160,6 +160,7 @@ dynamodb_local_latest/* # Build files Pipfile.lock +uv.lock # client-side things curation/client/node_modules diff --git a/client/src/components/Pages/Assay/Assay.tsx b/client/src/components/Pages/Assay/Assay.tsx index 29e45592..8196a227 100644 --- a/client/src/components/Pages/Assay/Assay.tsx +++ b/client/src/components/Pages/Assay/Assay.tsx @@ -6,8 +6,11 @@ import { Box, FormControl, FormControlLabel, + InputLabel, + MenuItem, Radio, RadioGroup, + Select, TextField, Typography, } from "@material-ui/core"; @@ -20,6 +23,25 @@ interface Props { index: number; } +const ASSAY_OPTIONS = [ + { + name: "fluorescence in-situ hybridization assay", + identifier: "obi:OBI_0003094", + }, + { name: "sequencing assay", identifier: "obi:OBI_0600047" }, + { name: "DNA sequencing assay", identifier: "obi:OBI_0000626" }, + { name: "RNA-seq assay", identifier: "obi:OBI_0001271" }, + { + name: "comparative genomic hybridization by array assay", + identifier: "obi:OBI_0001393", + }, + { + name: "RT-PCR", + identifier: "obi:OBI_0000552", + }, + { name: "custom", identifier: "" }, +]; + export const Assay: React.FC = () => { const { colorTheme } = useColorTheme(); const useStyles = makeStyles(() => ({ @@ -69,6 +91,17 @@ export const Assay: React.FC = () => { const [assayName, setAssayName] = useState( fusion?.assay?.assayName !== undefined ? fusion?.assay?.assayName : "" ); + const [selectedAssayOption, setSelectedAssayOption] = useState(() => { + return ( + ASSAY_OPTIONS.find((a) => a.name === assayName) || { + name: "", + identifier: "", + } + ); + }); + const isCustom = selectedAssayOption + ? selectedAssayOption?.name === "custom" + : false; const [assayId, setAssayId] = useState( fusion?.assay?.assayId !== undefined ? fusion?.assay?.assayId : "" @@ -89,6 +122,7 @@ export const Assay: React.FC = () => { }; const propertySetterMap = { + assaySelectedOption: [setSelectedAssayOption, "assaySelectedOption"], assayName: [setAssayName, "assayName"], assayId: [setAssayId, "assayId"], methodUri: [setMethodUri, "methodUri"], @@ -116,6 +150,19 @@ export const Assay: React.FC = () => { const setterFunction: CallableFunction = propertySetterMap[propertyName][0]; const jsonName: string = propertySetterMap[propertyName][1]; const assay: FusionAssay = JSON.parse(JSON.stringify(fusion.assay)); + if (propertyName === "assaySelectedOption") { + value = ASSAY_OPTIONS.find((a) => a.name === value); + setterFunction(value); + const newAssayName = value.name === "custom" ? "" : value?.name; + const newAssayId = value.identifier; + + setAssayId(newAssayId); + setAssayName(newAssayName); + assay["assayId"] = newAssayId; + assay["assayName"] = newAssayName; + setFusion({ ...fusion, assay: assay }); + return; + } if (value !== assay[jsonName]) { setterFunction(value); assay[jsonName] = value; @@ -168,6 +215,24 @@ export const Assay: React.FC = () => { Provide assay metadata: + Select assay + + = () => { handleValueChange("assayName", event.target.value) } @@ -201,6 +267,7 @@ export const Assay: React.FC = () => { label="Assay ID" margin="dense" value={assayId} + disabled={!isCustom} onChange={(event) => handleValueChange("assayId", event.target.value) } diff --git a/client/src/components/Pages/Domains/DomainForm/DomainForm.tsx b/client/src/components/Pages/Domains/DomainForm/DomainForm.tsx index 442e305b..5998aba7 100644 --- a/client/src/components/Pages/Domains/DomainForm/DomainForm.tsx +++ b/client/src/components/Pages/Domains/DomainForm/DomainForm.tsx @@ -94,7 +94,7 @@ const DomainForm: React.FC = () => { return [].concat( Object.keys(domainOptions).map((geneId: string, index: number) => ( - {`${globalGenes[geneId].label}(${geneId})`} + {`${globalGenes[geneId].name}(${geneId})`} )) ); diff --git a/client/src/components/Pages/Domains/Main/Domains.tsx b/client/src/components/Pages/Domains/Main/Domains.tsx index 197ad832..10bbcafa 100644 --- a/client/src/components/Pages/Domains/Main/Domains.tsx +++ b/client/src/components/Pages/Domains/Main/Domains.tsx @@ -108,7 +108,7 @@ export const Domain: React.FC = () => { avatar={{domain.status === "preserved" ? "P" : "L"}} label={ - {domainLabelString} {`(${domain.associatedGene.label})`} + {domainLabelString} {`(${domain.associatedGene.name})`} } onDelete={() => handleRemove(domain)} diff --git a/client/src/components/Pages/Gene/StructureDiagram/StructureDiagram.tsx b/client/src/components/Pages/Gene/StructureDiagram/StructureDiagram.tsx index e76d335a..0562dec8 100644 --- a/client/src/components/Pages/Gene/StructureDiagram/StructureDiagram.tsx +++ b/client/src/components/Pages/Gene/StructureDiagram/StructureDiagram.tsx @@ -43,7 +43,7 @@ export const StructureDiagram: React.FC = () => { const regEls = []; suggestion.regulatoryElements.forEach((el) => { - regEls.push(el.gene.label); + regEls.push(el.gene.name); }); return ( diff --git a/client/src/components/Pages/Structure/Input/GeneElementInput/GeneElementInput.tsx b/client/src/components/Pages/Structure/Input/GeneElementInput/GeneElementInput.tsx index a17a90a2..1b6a8eeb 100644 --- a/client/src/components/Pages/Structure/Input/GeneElementInput/GeneElementInput.tsx +++ b/client/src/components/Pages/Structure/Input/GeneElementInput/GeneElementInput.tsx @@ -23,7 +23,7 @@ const GeneElementInput: React.FC = ({ icon, }) => { const [errors, setErrors] = useState([]); - const [gene, setGene] = useState(element.gene?.label || ""); + const [gene, setGene] = useState(element.gene?.name || ""); const [geneText, setGeneText] = useState(""); const validated = gene !== "" && geneText == ""; const [expanded, setExpanded] = useState(!validated); diff --git a/client/src/components/Pages/Structure/Input/RegulatoryElementInput/RegulatoryElementInput.tsx b/client/src/components/Pages/Structure/Input/RegulatoryElementInput/RegulatoryElementInput.tsx index bf383260..f834a26f 100644 --- a/client/src/components/Pages/Structure/Input/RegulatoryElementInput/RegulatoryElementInput.tsx +++ b/client/src/components/Pages/Structure/Input/RegulatoryElementInput/RegulatoryElementInput.tsx @@ -57,47 +57,90 @@ const RegulatoryElementInput: React.FC = ({ const [elementClass, setElementClass] = useState( regElement?.regulatoryClass || "default" ); + const [featureId, setFeatureId] = useState( + regElement?.featureId || "" + ); const [gene, setGene] = useState( - regElement?.associatedGene?.label || "" + regElement?.associatedGene?.name || "" ); const [geneText, setGeneText] = useState(""); - const validated = gene !== "" && geneText == "" && elementClass !== "default"; + const [chromosome, setChromosome] = useState( + regElement?.featureLocation?.name || "" + ); + const [genomicStart, setGenomicStart] = useState(() => { + const start = regElement?.featureLocation?.start; + + if (typeof start === "number") { + return String(start + 1); + } + + return ""; + }); + const [genomicEnd, setGenomicEnd] = useState(() => { + const end = regElement?.featureLocation?.end; + + if (typeof end == "number") { + return String(end); + } + return ""; + }); + + const validated = + (gene !== "" && geneText == "" && elementClass !== "default") || + (chromosome !== "" && genomicStart !== "" && genomicEnd !== ""); const [expanded, setExpanded] = useState(!validated); const [errors, setErrors] = useState([]); useEffect(() => { if (validated) handleAdd(); - }, [gene, geneText, elementClass]); + }, [ + gene, + geneText, + elementClass, + featureId, + chromosome, + genomicStart, + genomicEnd, + ]); const handleAdd = () => { if (elementClass === "default") return; - getRegulatoryElement(elementClass, gene).then((reResponse) => { + getRegulatoryElement( + elementClass, + gene, + featureId, + chromosome, + genomicStart, + genomicEnd + ).then((reResponse) => { if (reResponse.warnings && reResponse.warnings.length > 0) { setErrors(reResponse.warnings); return; } - getRegElementNomenclature(reResponse.regulatoryElement).then( - (nomenclatureResponse) => { - if ( - nomenclatureResponse.warnings && - nomenclatureResponse.warnings.length > 0 - ) { - setErrors(nomenclatureResponse.warnings); - return; + if (reResponse.regulatoryElement) { + getRegElementNomenclature(reResponse.regulatoryElement).then( + (nomenclatureResponse) => { + if ( + nomenclatureResponse.warnings && + nomenclatureResponse.warnings.length > 0 + ) { + setErrors(nomenclatureResponse.warnings); + return; + } + setErrors([]); + const newRegElement: ClientRegulatoryElement = { + ...reResponse.regulatoryElement, + elementId: element.elementId, + displayClass: regulatoryClassItems[elementClass][1], + nomenclature: nomenclatureResponse.nomenclature || "", + }; + setRegElement(newRegElement); + setFusion({ ...fusion, ...{ regulatoryElement: newRegElement } }); } - setErrors([]); - const newRegElement: ClientRegulatoryElement = { - ...reResponse.regulatoryElement, - elementId: element.elementId, - displayClass: regulatoryClassItems[elementClass][1], - nomenclature: nomenclatureResponse.nomenclature || "", - }; - setRegElement(newRegElement); - setFusion({ ...fusion, ...{ regulatoryElement: newRegElement } }); - } - ); + ); + } }); }; @@ -107,8 +150,12 @@ const RegulatoryElementInput: React.FC = ({ setRegElement(undefined); setFusion(cloneFusion); setElementClass("default"); + setFeatureId(""); setGene(""); setGeneText(""); + setChromosome(""); + setGenomicStart(""); + setGenomicEnd(""); setErrors([]); }; @@ -118,10 +165,18 @@ const RegulatoryElementInput: React.FC = ({ regulatoryClassItems={regulatoryClassItems} elementClass={elementClass} setElementClass={setElementClass} + featureId={featureId} + setFeatureId={setFeatureId} gene={gene} setGene={setGene} geneText={geneText} setGeneText={setGeneText} + chromosome={chromosome} + setChromosome={setChromosome} + genomicStart={genomicStart} + setGenomicStart={setGenomicStart} + genomicEnd={genomicEnd} + setGenomicEnd={setGenomicEnd} /> ); diff --git a/client/src/components/Pages/Structure/Input/TemplatedSequenceElementInput/TemplatedSequenceElementInput.tsx b/client/src/components/Pages/Structure/Input/TemplatedSequenceElementInput/TemplatedSequenceElementInput.tsx index 435e7805..683eba1a 100644 --- a/client/src/components/Pages/Structure/Input/TemplatedSequenceElementInput/TemplatedSequenceElementInput.tsx +++ b/client/src/components/Pages/Structure/Input/TemplatedSequenceElementInput/TemplatedSequenceElementInput.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect, KeyboardEvent } from "react"; -import { TextField, Box, Typography } from "@material-ui/core"; +import { TextField, Typography } from "@material-ui/core"; import { StructuralElementInputProps } from "../StructuralElementInputProps"; import { getTemplatedSequenceElement, @@ -7,8 +7,8 @@ import { } from "../../../../../services/main"; import { ClientTemplatedSequenceElement } from "../../../../../services/ResponseModels"; import StructuralElementInputAccordion from "../StructuralElementInputAccordion"; -import StrandSwitch from "../../../../main/shared/StrandSwitch/StrandSwitch"; import HelpTooltip from "../../../../main/shared/HelpTooltip/HelpTooltip"; +import ChromosomeField from "../../../../main/shared/ChromosomeField/ChromosomeField"; interface TemplatedSequenceElementInputProps extends StructuralElementInputProps { @@ -106,38 +106,19 @@ const TemplatedSequenceElementInput: React.FC< const inputElements = ( <>
- - The chromosome on which the segment lies. - - RefSeq identifiers (e.g.{" "} - NC_000001.11) are - preferred. - - + + setChromosome(event.target.value as string) } - > - setChromosome(event.target.value)} - onKeyDown={handleEnterKey} - /> - - - - + />
- The starting genomic position (inter-residue) of the segment. + The starting genomic position (residue) of the segment. } > @@ -154,7 +135,7 @@ const TemplatedSequenceElementInput: React.FC< placement="bottom" title={ - The ending genomic position (inter-residue) of the segment. + The ending genomic position (residue) of the segment. } > diff --git a/client/src/components/Pages/Structure/Input/TxSegmentElementInput/TxSegmentElementInput.tsx b/client/src/components/Pages/Structure/Input/TxSegmentElementInput/TxSegmentElementInput.tsx index ebff92e9..f55ec42a 100644 --- a/client/src/components/Pages/Structure/Input/TxSegmentElementInput/TxSegmentElementInput.tsx +++ b/client/src/components/Pages/Structure/Input/TxSegmentElementInput/TxSegmentElementInput.tsx @@ -26,6 +26,10 @@ import StructuralElementInputAccordion from "../StructuralElementInputAccordion" import HelpTooltip from "../../../../main/shared/HelpTooltip/HelpTooltip"; import ChromosomeField from "../../../../main/shared/ChromosomeField/ChromosomeField"; import TranscriptField from "../../../../main/shared/TranscriptField/TranscriptField"; +import StrandSwitch from "../../../../main/shared/StrandSwitch/StrandSwitch"; +import GeneTranscriptSelector from "../../../../main/shared/GeneTranscriptSelector/GeneTranscriptSelector"; +import { TxGenomicCoords } from "../../../../main/shared/TxGenomicCoords/TxGenomicCoords"; +import { setNumericField } from "../../../../Utilities/SetNumericField/SetNumericField"; interface TxSegmentElementInputProps extends StructuralElementInputProps { element: ClientTranscriptSegmentElement; @@ -152,6 +156,9 @@ const TxSegmentCompInput: React.FC = ({ ...responseElement, ...inputParams, }; + if (responseElement?.strand) { + setTxStrand(responseElement.strand === 1 ? "+" : "-"); + } if (!hasRequiredEnds) { finishedElement.nomenclature = "ERROR"; } else { @@ -301,93 +308,6 @@ const TxSegmentCompInput: React.FC = ({ } }; - /** - * Handle pre-request validation for a numeric input field - * @param value user-entered value - * @param warnSetter useState setter function for warning text - * @param valueSetter useState value setter function - * @param positive if true, must be >= 0 - */ - const setNumericField = ( - value: string, - warnSetter: CallableFunction, - valueSetter: CallableFunction, - positive: boolean - ) => { - const re = positive ? /^[0-9]*$/ : /^\-?[0-9]*$/; - if (!value.match(re)) { - warnSetter(`${positive ? "Nonzero i" : "I"}nteger required`); - } else { - warnSetter(""); - } - valueSetter(value); - }; - - /** - * Render transcript segment genomic coordinate fields - * @returns start and end position input TextFields - */ - const renderTxGenomicCoords = () => ( - <> - - The starting genomic position (inter-residue) of the transcript - segment. - - } - > - - setNumericField( - event.target.value, - setTxStartingGenomicText, - setTxStartingGenomic, - true - ) - } - onKeyDown={handleEnterKey} - error={txStartingGenomicText !== ""} - helperText={ - txStartingGenomicText !== "" ? txStartingGenomicText : null - } - /> - - - The ending genomic position (inter-residue) of the transcript - segment. - - } - > - - setNumericField( - event.target.value, - setTxEndingGenomicText, - setTxEndingGenomic, - true - ) - } - onKeyDown={handleEnterKey} - error={txEndingGenomicText !== ""} - helperText={txEndingGenomicText !== "" ? txEndingGenomicText : null} - /> - - - ); - const txInputField = ( = ({ ); - const handleChromosomeChange = (e: ChangeEvent) => { - setTxChrom(e.target.value); + const handleChromosomeChange = ( + e: ChangeEvent<{ name?: string; value: unknown }> + ) => { + setTxChrom(e.target.value as string); }; const genomicCoordinateInfo = ( @@ -410,8 +332,25 @@ const TxSegmentCompInput: React.FC = ({ fieldValue={txChrom} onChange={handleChromosomeChange} /> + + + + + + - {renderTxGenomicCoords()} ); @@ -437,26 +376,11 @@ const TxSegmentCompInput: React.FC = ({ setTranscripts={setGeneTranscripts} setDefaultTranscript={setTxAc} /> - - - Transcript - - - + ) : ( <> @@ -470,7 +394,25 @@ const TxSegmentCompInput: React.FC = ({ case TxElementInputType.ec: return ( - {txInputField} + + + + + ({ formControl: { @@ -31,23 +39,42 @@ interface Props { regulatoryClassItems: object; elementClass: RegulatoryClass | "default"; setElementClass: CallableFunction; + featureId: string; + setFeatureId: CallableFunction; gene: string; setGene: CallableFunction; geneText: string; setGeneText: CallableFunction; + chromosome: string; + setChromosome: CallableFunction; + genomicStart: string; + setGenomicStart: Setter; + genomicEnd: string; + setGenomicEnd: Setter; } const RegElementForm: React.FC = ({ regulatoryClassItems, elementClass, setElementClass, + featureId, + setFeatureId, gene, setGene, geneText, setGeneText, + chromosome, + setChromosome, + genomicStart, + setGenomicStart, + genomicEnd, + setGenomicEnd, }) => { const classes = useStyles(); + const [txStartingGenomicText, setTxStartingGenomicText] = useState(""); + const [txEndingGenomicText, setTxEndingGenomicText] = useState(""); + /** * Construct the regulatory class menu item array. * @returns list of MenuItems @@ -64,27 +91,79 @@ const RegElementForm: React.FC = ({ )); }; + const handleChromosomeChange = ( + e: ChangeEvent<{ name?: string; value: unknown }> + ) => { + setChromosome(e.target.value as string); + }; + + const genomicCoordinateInfo = ( + <> + + + + + + + + ); + return (
- - Class +
+ + Class + INSDC regulatory class vocabulary term. + } + > + + + INSDC regulatory class vocabulary term. + + An optional identifier for the regulatory feature, e.g. registered + cis-regulatory elements from ENCODE. + } > - + setFeatureId(event.target.value)} + /> - +
= ({ setGeneText={setGeneText} tooltipDirection="left" /> + + {genomicCoordinateInfo}
); }; diff --git a/client/src/components/Pages/Summary/Main/Summary.tsx b/client/src/components/Pages/Summary/Main/Summary.tsx index 976938ea..da620709 100644 --- a/client/src/components/Pages/Summary/Main/Summary.tsx +++ b/client/src/components/Pages/Summary/Main/Summary.tsx @@ -81,6 +81,8 @@ export const Summary: React.FC = ({ setVisibleTab }) => { gene: element.gene, elementGenomicStart: element.elementGenomicStart, elementGenomicEnd: element.elementGenomicEnd, + strand: element.strand, + transcriptStatus: element.transcriptStatus, }; return txSegmentElement; case "MultiplePossibleGenesElement": diff --git a/client/src/components/Pages/Summary/Readable/Readable.tsx b/client/src/components/Pages/Summary/Readable/Readable.tsx index 2bed053a..6e7dec5a 100644 --- a/client/src/components/Pages/Summary/Readable/Readable.tsx +++ b/client/src/components/Pages/Summary/Readable/Readable.tsx @@ -37,6 +37,8 @@ export const Readable: React.FC = ({ }, [formattedFusion]); const assayName = fusion.assay?.assayName ? fusion.assay.assayName : ""; + const fusionDetection = fusion.assay?.fusionDetection; + const assayRowName = fusionDetection ? `Assay (${fusionDetection})` : "Assay"; const assayId = fusion.assay?.assayId ? `(${fusion.assay.assayId})` : ""; /** @@ -57,7 +59,7 @@ export const Readable: React.FC = ({ - Assay + {assayRowName} diff --git a/client/src/components/Utilities/GetCoordinates/GetCoordinates.tsx b/client/src/components/Utilities/GetCoordinates/GetCoordinates.tsx index f9edd565..8d23f023 100644 --- a/client/src/components/Utilities/GetCoordinates/GetCoordinates.tsx +++ b/client/src/components/Utilities/GetCoordinates/GetCoordinates.tsx @@ -31,6 +31,8 @@ import ChromosomeField from "../../main/shared/ChromosomeField/ChromosomeField"; import TranscriptField from "../../main/shared/TranscriptField/TranscriptField"; import LoadingMessage from "../../main/shared/LoadingMessage/LoadingMessage"; import HelpTooltip from "../../main/shared/HelpTooltip/HelpTooltip"; +import StrandSwitch from "../../main/shared/StrandSwitch/StrandSwitch"; +import GeneTranscriptSelector from "../../main/shared/GeneTranscriptSelector/GeneTranscriptSelector"; const GetCoordinates: React.FC = () => { const useStyles = makeStyles(() => ({ @@ -68,6 +70,9 @@ const GetCoordinates: React.FC = () => { flexDirection: "row", alignItems: "center", }, + strandSwitchLabel: { + marginLeft: "0 !important", + }, coordsCard: { margin: "10px", }, @@ -230,6 +235,15 @@ const GetCoordinates: React.FC = () => { ); + useEffect(() => { + if (results) { + const resultStrand = results.strand === 1 ? "+" : "-"; + if (resultStrand !== strand) { + setStrand(resultStrand); + } + } + }, [results]); + const renderResults = (): React.ReactFragment => { if (isLoading) { return ; @@ -294,8 +308,10 @@ const GetCoordinates: React.FC = () => { /> ); - const handleChromosomeChange = (e: ChangeEvent) => { - setChromosome(e.target.value); + const handleChromosomeChange = ( + e: ChangeEvent<{ name?: string; value: unknown }> + ) => { + setChromosome(e.target.value as string); }; const genomicCoordinateInfo = ( @@ -305,6 +321,17 @@ const GetCoordinates: React.FC = () => { fieldValue={chromosome} onChange={handleChromosomeChange} /> + + + + +
); @@ -330,24 +357,12 @@ const GetCoordinates: React.FC = () => { setTranscripts={setGeneTranscripts} setDefaultTranscript={setSelectedTranscript} /> - - Transcript - - + + ) : ( <> @@ -361,8 +376,8 @@ const GetCoordinates: React.FC = () => { placement="bottom" title={ - The starting genomic position (inter-residue) of the - transcript segment. + The starting genomic position (residue) of the transcript + segment. } > @@ -378,8 +393,8 @@ const GetCoordinates: React.FC = () => { placement="bottom" title={ - The ending genomic position (inter-residue) of the - transcript segment. + The ending genomic position (residue) of the transcript + segment. } > @@ -396,7 +411,24 @@ const GetCoordinates: React.FC = () => { case TxElementInputType.ec: return ( <> - {txInputField} + + + + + { "Ensembl Transcript": transcript.Ensembl_nuc, "Ensembl Protein": transcript.Ensembl_prot, Chromosome: transcript.GRCh38_chr, - Start: transcript.chr_start, - End: transcript.chr_end, + "Genomic Start Position (residue)": transcript.chr_start, + "Genomic End Position (residue)": transcript.chr_end, Strand: transcript.chr_strand, }; return ( diff --git a/client/src/components/Utilities/SetNumericField/SetNumericField.tsx b/client/src/components/Utilities/SetNumericField/SetNumericField.tsx new file mode 100644 index 00000000..22ba92fc --- /dev/null +++ b/client/src/components/Utilities/SetNumericField/SetNumericField.tsx @@ -0,0 +1,21 @@ +/** + * Handle pre-request validation for a numeric input field + * @param value user-entered value + * @param warnSetter useState setter function for warning text + * @param valueSetter useState value setter function + * @param positive if true, must be >= 0 + */ +export const setNumericField = ( + value: string, + warnSetter: CallableFunction, + valueSetter: CallableFunction, + positive: boolean +) => { + const re = positive ? /^[0-9]*$/ : /^\-?[0-9]*$/; + if (!value.match(re)) { + warnSetter(`${positive ? "Nonzero i" : "I"}nteger required`); + } else { + warnSetter(""); + } + valueSetter(value); +}; diff --git a/client/src/components/main/App/App.tsx b/client/src/components/main/App/App.tsx index 34056944..fe51fce5 100644 --- a/client/src/components/main/App/App.tsx +++ b/client/src/components/main/App/App.tsx @@ -102,19 +102,22 @@ const App = (): JSX.Element => { comp.type && (comp.type === "GeneElement" || comp.type === "TranscriptSegmentElement") && - comp.gene?.id + comp.gene?.primaryCoding?.id ) { - remainingGeneIds.push(comp.gene.id); - if (comp.gene.id && !(comp.gene.id in globalGenes)) { - newGenes[comp.gene.id] = comp.gene; + const compGeneId = comp.gene.primaryCoding.id; + remainingGeneIds.push(compGeneId); + if (compGeneId && !(compGeneId in globalGenes)) { + newGenes[compGeneId] = comp.gene; } } }); if (fusion.regulatoryElement) { - if (fusion.regulatoryElement.associatedGene?.id) { - remainingGeneIds.push(fusion.regulatoryElement.associatedGene.id); - if (!(fusion.regulatoryElement.associatedGene.id in globalGenes)) { - newGenes[fusion.regulatoryElement.associatedGene.id] = + const regulatoryElementGeneId = + fusion.regulatoryElement.associatedGene?.primaryCoding?.id; + if (regulatoryElementGeneId) { + remainingGeneIds.push(regulatoryElementGeneId); + if (!(regulatoryElementGeneId in globalGenes)) { + newGenes[regulatoryElementGeneId] = fusion.regulatoryElement.associatedGene; } } diff --git a/client/src/components/main/shared/ChromosomeField/ChromosomeField.tsx b/client/src/components/main/shared/ChromosomeField/ChromosomeField.tsx index 7c934451..5c008a66 100644 --- a/client/src/components/main/shared/ChromosomeField/ChromosomeField.tsx +++ b/client/src/components/main/shared/ChromosomeField/ChromosomeField.tsx @@ -1,44 +1,85 @@ -import { makeStyles, TextField, Typography } from "@material-ui/core"; -import React, { ChangeEvent } from "react"; +import { + FormControl, + InputLabel, + MenuItem, + Select, + Typography, +} from "@material-ui/core"; +import React, { ChangeEvent, ReactNode } from "react"; import HelpTooltip from "../HelpTooltip/HelpTooltip"; +const REFSEQ_CHROMOSOME_IDENTIFIERS = [ + { identifier: "NC_000001.11", shorthand: "chr1" }, + { identifier: "NC_000002.12", shorthand: "chr2" }, + { identifier: "NC_000003.12", shorthand: "chr3" }, + { identifier: "NC_000004.12", shorthand: "chr4" }, + { identifier: "NC_000005.10", shorthand: "chr5" }, + { identifier: "NC_000006.12", shorthand: "chr6" }, + { identifier: "NC_000007.14", shorthand: "chr7" }, + { identifier: "NC_000008.11", shorthand: "chr8" }, + { identifier: "NC_000009.12", shorthand: "chr9" }, + { identifier: "NC_000010.11", shorthand: "chr10" }, + { identifier: "NC_000011.10", shorthand: "chr11" }, + { identifier: "NC_000012.12", shorthand: "chr12" }, + { identifier: "NC_000013.11", shorthand: "chr13" }, + { identifier: "NC_000014.9", shorthand: "chr14" }, + { identifier: "NC_000015.10", shorthand: "chr15" }, + { identifier: "NC_000016.10", shorthand: "chr16" }, + { identifier: "NC_000017.11", shorthand: "chr17" }, + { identifier: "NC_000018.10", shorthand: "chr18" }, + { identifier: "NC_000019.10", shorthand: "chr19" }, + { identifier: "NC_000020.11", shorthand: "chr20" }, + { identifier: "NC_000021.9", shorthand: "chr21" }, + { identifier: "NC_000022.11", shorthand: "chr22" }, + { identifier: "NC_000023.11", shorthand: "chrX" }, + { identifier: "NC_000024.10", shorthand: "chrY" }, +]; + interface Props { fieldValue: string; width?: number | undefined; editable?: boolean; - onChange?: (event: ChangeEvent) => void; + onChange?: ( + event: ChangeEvent<{ name?: string; value: unknown }>, + child: ReactNode + ) => void; } -const ChromosomeField: React.FC = ({ fieldValue, width, onChange }) => { - const useStyles = makeStyles(() => ({ - textField: { - height: 38, - width: width ? width : 125, - }, - })); - const classes = useStyles(); - +const ChromosomeField: React.FC = ({ + fieldValue, + editable = true, + onChange, +}) => { return ( The chromosome on which the segment lies. - RefSeq identifiers (e.g.{" "} + Only GRCh38 RefSeq identifiers (e.g.{" "} NC_000001.11) are - preferred. + supported. } > - + + Chromosome + + ); }; diff --git a/client/src/components/main/shared/GeneAutocomplete/GeneAutocomplete.tsx b/client/src/components/main/shared/GeneAutocomplete/GeneAutocomplete.tsx index d15bca10..dfe1145f 100644 --- a/client/src/components/main/shared/GeneAutocomplete/GeneAutocomplete.tsx +++ b/client/src/components/main/shared/GeneAutocomplete/GeneAutocomplete.tsx @@ -12,6 +12,7 @@ import { GetGeneTranscriptsResponse, GetTranscriptsResponse, SuggestGeneResponse, + ManeGeneTranscript, } from "../../../../services/ResponseModels"; import HelpTooltip from "../HelpTooltip/HelpTooltip"; import { useColorTheme } from "../../../../global/contexts/Theme/ColorThemeContext"; @@ -126,16 +127,57 @@ export const GeneAutocomplete: React.FC = ({ const sortedTranscripts = transcripts.sort((a, b) => a.localeCompare(b) ); - setTranscripts(sortedTranscripts); if (setDefaultTranscript) { // get preferred default transcript from MANE endpoint getTranscripts(selection.value).then( (transcriptsResponse: GetTranscriptsResponse) => { + if (transcriptsResponse?.transcripts) { + const maneMap = new Map( + transcriptsResponse.transcripts.map( + (item: ManeGeneTranscript) => [ + item.RefSeq_nuc, + item.MANE_status, + ] + ) + ); + + const annotatedTranscripts = sortedTranscripts.map( + (transcript) => ({ + transcript, + maneStatus: maneMap.get(transcript) ?? null, + }) + ); + + // Sort by MANE priority (select > plus clinical > none), then alphabetically + const sortedByStatus = annotatedTranscripts.sort((a, b) => { + const rank = (status: string | null) => { + if (status === "MANE Select") return 0; + if (status === "MANE Plus Clinical") return 1; + return 2; + }; + + const diff = rank(a.maneStatus) - rank(b.maneStatus); + return diff !== 0 + ? diff + : a.transcript.localeCompare(b.transcript); + }); + + setTranscripts(sortedByStatus); + } + const preferredTx = transcriptsResponse?.transcripts?.[0].RefSeq_nuc; setDefaultTranscript(preferredTx || transcripts[0]); } ); + } else { + const annotatedTranscripts = sortedTranscripts.map( + (transcript) => ({ + transcript, + maneStatus: null, + }) + ); + setTranscripts(annotatedTranscripts); } } ); diff --git a/client/src/components/main/shared/GeneTranscriptSelector/GeneTranscriptSelector.tsx b/client/src/components/main/shared/GeneTranscriptSelector/GeneTranscriptSelector.tsx new file mode 100644 index 00000000..86a355cc --- /dev/null +++ b/client/src/components/main/shared/GeneTranscriptSelector/GeneTranscriptSelector.tsx @@ -0,0 +1,45 @@ +import React from "react"; +import { FormControl, InputLabel, MenuItem, Select } from "@material-ui/core"; + +export interface Transcript { + transcript: string; + maneStatus: string | null; +} + +interface Props { + transcript: string; + onTranscriptChange: ( + event: React.ChangeEvent<{ name?: string; value: unknown }>, + child: React.ReactNode + ) => void; + transcripts: Transcript[]; +} + +const GeneTranscriptSelector: React.FC = ({ + transcript, + onTranscriptChange, + transcripts, +}) => { + return ( + + Transcript + + + ); +}; + +export default GeneTranscriptSelector; diff --git a/client/src/components/main/shared/LoadingMessage/LoadingMessage.tsx b/client/src/components/main/shared/LoadingMessage/LoadingMessage.tsx index 9274dbb7..eafaf631 100644 --- a/client/src/components/main/shared/LoadingMessage/LoadingMessage.tsx +++ b/client/src/components/main/shared/LoadingMessage/LoadingMessage.tsx @@ -5,7 +5,7 @@ interface LoadingMessageProps { message?: string; } -export default function StrandSwitch( +export default function LoadingMessage( props: LoadingMessageProps ): React.ReactElement { const loadingMessage = props?.message ? props.message : "Loading..."; diff --git a/client/src/components/main/shared/StrandSwitch/StrandSwitch.tsx b/client/src/components/main/shared/StrandSwitch/StrandSwitch.tsx index 2f936ea0..36f8b62e 100644 --- a/client/src/components/main/shared/StrandSwitch/StrandSwitch.tsx +++ b/client/src/components/main/shared/StrandSwitch/StrandSwitch.tsx @@ -37,7 +37,7 @@ export default function StrandSwitch( checked={selectedStrand === "-"} icon={} checkedIcon={} - disableRipple + disabled /> } label="Strand" diff --git a/client/src/components/main/shared/TxGenomicCoords/TxGenomicCoords.tsx b/client/src/components/main/shared/TxGenomicCoords/TxGenomicCoords.tsx new file mode 100644 index 00000000..8bfe31f2 --- /dev/null +++ b/client/src/components/main/shared/TxGenomicCoords/TxGenomicCoords.tsx @@ -0,0 +1,94 @@ +import React from "react"; +import { TextField, Typography, Box } from "@mui/material"; +import HelpTooltip from "../HelpTooltip/HelpTooltip"; + +export type Setter = React.Dispatch>; + +interface TxGenomicCoordsProps { + component: string; + genomicStart: string; + genomicEnd: string; + txStartingGenomicText?: string; + txEndingGenomicText?: string; + setTxStartingGenomicText: Setter; + setTxEndingGenomicText: Setter; + setGenomicStart: Setter; + setGenomicEnd: Setter; + setNumericField: ( + val: string, + warnSetter: Setter, + valueSetter: Setter, + positive: boolean + ) => void; + handleEnterKey?: (e: React.KeyboardEvent) => void; +} + +export const TxGenomicCoords: React.FC = ({ + component, + genomicStart, + genomicEnd, + txStartingGenomicText, + txEndingGenomicText, + setTxStartingGenomicText, + setTxEndingGenomicText, + setGenomicStart, + setGenomicEnd, + setNumericField, + handleEnterKey, +}) => { + return ( + + {`The starting genomic position (residue) of the ${component}.`} + } + > + + setNumericField( + e.target.value, + setTxStartingGenomicText, + setGenomicStart, + true + ) + } + onKeyDown={handleEnterKey} + error={!!txStartingGenomicText} + helperText={txStartingGenomicText || ""} + /> + + + {`The ending genomic position (residue) of the ${component}.`} + } + > + + setNumericField( + e.target.value, + setTxEndingGenomicText, + setGenomicEnd, + true + ) + } + onKeyDown={handleEnterKey} + error={!!txEndingGenomicText} + helperText={txEndingGenomicText || ""} + /> + + + ); +}; diff --git a/client/src/services/ResponseModels.ts b/client/src/services/ResponseModels.ts index d6d40e28..498a3964 100644 --- a/client/src/services/ResponseModels.ts +++ b/client/src/services/ResponseModels.ts @@ -40,8 +40,11 @@ export type RegulatoryClass = */ export type Code = string; /** - * A mapping relation between concepts as defined by the Simple Knowledge - * Organization System (SKOS). + * An IRI Reference (either an IRI or a relative-reference), according to `RFC3986 section 4.1 `_ and `RFC3987 section 2.1 `_. MAY be a JSON Pointer as an IRI fragment, as described by `RFC6901 section 6 `_. + */ +export type IriReference = string; +/** + * A mapping relation between concepts as defined by the Simple Knowledge Organization System (SKOS). */ export type Relation = | "closeMatch" @@ -49,28 +52,40 @@ export type Relation = | "broadMatch" | "narrowMatch" | "relatedMatch"; -/** - * An IRI Reference (either an IRI or a relative-reference), according to `RFC3986 section 4.1 ` and `RFC3987 section 2.1 `. MAY be a JSON Pointer as an IRI fragment, as described by `RFC6901 section 6 `. - */ -export type IRI = string; /** * The interpretation of the character codes referred to by the refget accession, * where "aa" specifies an amino acid character set, and "na" specifies a nucleic acid * character set. */ export type ResidueAlphabet = "aa" | "na"; +/** + * A character string of Residues that represents a biological sequence using the conventional sequence order (5'-to-3' for nucleic acid sequences, and amino-to-carboxyl for amino acid sequences). IUPAC ambiguity codes are permitted in Sequence Strings. + */ +export type SequenceString = string; +/** + * Molecule types as `defined by RefSeq `_ (see Table 1). + */ +export type MoleculeType = "genomic" | "RNA" | "mRNA" | "protein"; /** * An inclusive range of values bounded by one or more integers. */ export type Range = [number | null, number | null]; /** - * A character string of Residues that represents a biological sequence using the conventional sequence order (5'-to-3' for nucleic acid sequences, and amino-to-carboxyl for amino acid sequences). IUPAC ambiguity codes are permitted in Sequence Strings. + * Create Enum for Transcript Priority labels */ -export type SequenceString = string; +export type TranscriptPriority = + | "mane_select" + | "mane_plus_clinical" + | "longest_compatible_remaining" + | "grch38"; /** * Create enum for positive and negative strand */ export type Strand = 1 | -1; +/** + * A character string of Residues that represents a biological sequence using the conventional sequence order (5'-to-3' for nucleic acid sequences, and amino-to-carboxyl for amino acid sequences). IUPAC ambiguity codes are permitted in Sequence Strings. + */ +export type SequenceString1 = string; /** * Permissible values for describing the underlying causative event driving an * assayed fusion. @@ -80,6 +95,20 @@ export type EventType = "rearrangement" | "read-through" | "trans-splicing"; * Define possible statuses of functional domains. */ export type DomainStatus = "lost" | "preserved"; +/** + * Assayed gene fusions from biological specimens are directly detected using + * RNA-based gene fusion assays, or alternatively may be inferred from genomic + * rearrangements detected by whole genome sequencing or by coarser-scale cytogenomic + * assays. Example: an EWSR1 fusion inferred from a breakapart FISH assay. + */ +export type AssayedFusion1 = string; +/** + * Categorical gene fusions are generalized concepts representing a class + * of fusions by their shared attributes, such as retained or lost regulatory + * elements and/or functional domains, and are typically curated from the + * biomedical literature for use in genomic knowledgebases. + */ +export type CategoricalFusion1 = string; /** * Information pertaining to the assay used in identifying the fusion. @@ -98,7 +127,6 @@ export interface Assay { * assays. Example: an EWSR1 fusion inferred from a breakapart FISH assay. */ export interface AssayedFusion { - type?: "AssayedFusion"; regulatoryElement?: RegulatoryElement | null; structure: ( | TranscriptSegmentElement @@ -106,10 +134,18 @@ export interface AssayedFusion { | TemplatedSequenceElement | LinkerElement | UnknownGeneElement + | ContigSequence + | ReadData )[]; + fivePrimeJunction?: string | null; + threePrimeJunction?: string | null; readingFramePreserved?: boolean | null; + type?: "AssayedFusion"; + viccNomenclature?: string | null; causativeEvent?: CausativeEvent | null; assay?: Assay | null; + contig?: ContigSequence | null; + readData?: ReadData | null; } /** * Define RegulatoryElement class. @@ -122,42 +158,37 @@ export interface RegulatoryElement { type?: "RegulatoryElement"; regulatoryClass: RegulatoryClass; featureId?: string | null; - associatedGene?: Gene | null; - featureLocation?: SequenceLocation | null; + associatedGene?: MappableConcept | null; + featureLocation?: GenomicLocation | null; } /** - * A basic physical and functional unit of heredity. + * A concept based on a primaryCoding and/or name that may be mapped to one or more other `Codings`. */ -export interface Gene { +export interface MappableConcept { /** - * The 'logical' identifier of the entity in the system of record, e.g. a UUID. This 'id' is unique within a given system. The identified entity may have a different 'id' in a different system, or may refer to an 'id' for the shared concept in another system (e.g. a CURIE). + * The 'logical' identifier of the data element in the system of record, e.g. a UUID. This 'id' is unique within a given system, but may or may not be globally unique outside the system. It is used within a system to reference an object from another. */ id?: string | null; /** - * MUST be "Gene". + * A list of extensions to the Entity, that allow for capture of information not directly supported by elements defined in the model. */ - type?: "Gene"; - /** - * A primary label for the entity. - */ - label?: string | null; + extensions?: Extension[] | null; /** - * A free-text description of the entity. + * A term indicating the type of concept being represented by the MappableConcept. */ - description?: string | null; + conceptType?: string | null; /** - * Alternative name(s) for the Entity. + * A primary name for the concept. */ - alternativeLabels?: string[] | null; + name?: string | null; /** - * A list of extensions to the entity. Extensions are not expected to be natively understood, but may be used for pre-negotiated exchange of message attributes between systems. + * A primary coding for the concept. */ - extensions?: Extension[] | null; + primaryCoding?: Coding | null; /** * A list of mappings to concepts in terminologies or code systems. Each mapping should include a coding and a relation. */ mappings?: ConceptMapping[] | null; - [k: string]: unknown; } /** * The Extension class provides entities with a means to include additional @@ -167,6 +198,14 @@ export interface Gene { * between systems. */ export interface Extension { + /** + * The 'logical' identifier of the data element in the system of record, e.g. a UUID. This 'id' is unique within a given system, but may or may not be globally unique outside the system. It is used within a system to reference an object from another. + */ + id?: string | null; + /** + * A list of extensions to the Entity, that allow for capture of information not directly supported by elements defined in the model. + */ + extensions?: Extension[] | null; /** * A name for the Extension. Should be indicative of its meaning and/or the type of information it value represents. */ @@ -174,7 +213,7 @@ export interface Extension { /** * The value of the Extension - can be any primitive or structured object */ - value?: + value: | number | string | boolean @@ -187,105 +226,136 @@ export interface Extension { * A description of the meaning or utility of the Extension, to explain the type of information it is meant to hold. */ description?: string | null; - [k: string]: unknown; +} +/** + * A structured representation of a code for a defined concept in a terminology or + * code system. + */ +export interface Coding { + /** + * The 'logical' identifier of the data element in the system of record, e.g. a UUID. This 'id' is unique within a given system, but may or may not be globally unique outside the system. It is used within a system to reference an object from another. + */ + id?: string | null; + /** + * A list of extensions to the Entity, that allow for capture of information not directly supported by elements defined in the model. + */ + extensions?: Extension[] | null; + /** + * The human-readable name for the coded concept, as defined by the code system. + */ + name?: string | null; + /** + * The terminology/code system that defined the code. May be reported as a free-text name (e.g. 'Sequence Ontology'), but it is preferable to provide a uri/url for the system. + */ + system: string; + /** + * Version of the terminology or code system that provided the code. + */ + systemVersion?: string | null; + code: Code; + /** + * A list of IRIs that are associated with the coding. This can be used to provide additional context or to link to additional information about the concept. + */ + iris?: IriReference[] | null; } /** * A mapping to a concept in a terminology or code system. */ export interface ConceptMapping { /** - * A structured representation of a code for a defined concept in a terminology or code system. + * The 'logical' identifier of the data element in the system of record, e.g. a UUID. This 'id' is unique within a given system, but may or may not be globally unique outside the system. It is used within a system to reference an object from another. */ - coding: Coding; + id?: string | null; /** - * A mapping relation between concepts as defined by the Simple Knowledge Organization System (SKOS). + * A list of extensions to the Entity, that allow for capture of information not directly supported by elements defined in the model. */ + extensions?: Extension[] | null; + coding: Coding1; relation: Relation; - [k: string]: unknown; } /** * A structured representation of a code for a defined concept in a terminology or * code system. */ -export interface Coding { +export interface Coding1 { + /** + * The 'logical' identifier of the data element in the system of record, e.g. a UUID. This 'id' is unique within a given system, but may or may not be globally unique outside the system. It is used within a system to reference an object from another. + */ + id?: string | null; + /** + * A list of extensions to the Entity, that allow for capture of information not directly supported by elements defined in the model. + */ + extensions?: Extension[] | null; /** * The human-readable name for the coded concept, as defined by the code system. */ - label?: string | null; + name?: string | null; /** - * The terminology/code system that defined the code. May be reported as a free-text name (e.g. 'Sequence Ontology'), but it is preferable to provide a uri/url for the system. When the 'code' is reported as a CURIE, the 'system' should be reported as the uri that the CURIE's prefix expands to (e.g. 'http://purl.obofoundry.org/so.owl/' for the Sequence Ontology). + * The terminology/code system that defined the code. May be reported as a free-text name (e.g. 'Sequence Ontology'), but it is preferable to provide a uri/url for the system. */ system: string; /** * Version of the terminology or code system that provided the code. */ - version?: string | null; + systemVersion?: string | null; + code: Code; /** - * A symbol uniquely identifying the concept, as in a syntax defined by the code system. CURIE format is preferred where possible (e.g. 'SO:0000704' is the CURIE form of the Sequence Ontology code for 'gene'). + * A list of IRIs that are associated with the coding. This can be used to provide additional context or to link to additional information about the concept. */ - code: Code; - [k: string]: unknown; + iris?: IriReference[] | null; } /** - * A `Location` defined by an interval on a referenced `Sequence`. + * Define GenomicLocation class */ -export interface SequenceLocation { +export interface GenomicLocation { /** - * The 'logical' identifier of the entity in the system of record, e.g. a UUID. This 'id' is unique within a given system. The identified entity may have a different 'id' in a different system, or may refer to an 'id' for the shared concept in another system (e.g. a CURIE). + * The 'logical' identifier of the Entity in the system of record, e.g. a UUID. This 'id' is unique within a given system, but may or may not be globally unique outside the system. It is used within a system to reference an object from another. */ id?: string | null; /** * MUST be "SequenceLocation" */ type?: "SequenceLocation"; + name: string; /** - * A primary label for the entity. - */ - label?: string | null; - /** - * A free-text description of the entity. + * A free-text description of the Entity. */ description?: string | null; /** * Alternative name(s) for the Entity. */ - alternativeLabels?: string[] | null; + aliases?: string[] | null; /** - * A list of extensions to the entity. Extensions are not expected to be natively understood, but may be used for pre-negotiated exchange of message attributes between systems. + * A list of extensions to the Entity, that allow for capture of information not directly supported by elements defined in the model. */ extensions?: Extension[] | null; - /** - * A list of mappings to concepts in terminologies or code systems. Each mapping should include a coding and a relation. - */ - mappings?: ConceptMapping[] | null; /** * A sha512t24u digest created using the VRS Computed Identifier algorithm. */ digest?: string | null; /** - * A reference to a `Sequence` on which the location is defined. + * A reference to a SequenceReference on which the location is defined. */ - sequenceReference?: IRI | SequenceReference | null; + sequenceReference?: IriReference | SequenceReference | null; /** - * The start coordinate or range of the SequenceLocation. The minimum value of this coordinate or range is 0. MUST represent a coordinate or range less than the value of `end`. + * The start coordinate or range of the SequenceLocation. The minimum value of this coordinate or range is 0. For locations on linear sequences, this MUST represent a coordinate or range less than or equal to the value of `end`. For circular sequences, `start` is greater than `end` when the location spans the sequence 0 coordinate. */ start?: Range | number | null; /** - * The end coordinate or range of the SequenceLocation. The minimum value of this coordinate or range is 0. MUST represent a coordinate or range greater than the value of `start`. + * The end coordinate or range of the SequenceLocation. The minimum value of this coordinate or range is 0. For locations on linear sequences, this MUST represent a coordinate or range greater than or equal to the value of `start`. For circular sequences, `end` is less than `start` when the location spans the sequence 0 coordinate. */ end?: Range | number | null; /** * The literal sequence encoded by the `sequenceReference` at these coordinates. */ sequence?: SequenceString | null; - [k: string]: unknown; } /** * A sequence of nucleic or amino acid character codes. */ export interface SequenceReference { /** - * The 'logical' identifier of the entity in the system of record, e.g. a UUID. This 'id' is unique within a given system. The identified entity may have a different 'id' in a different system, or may refer to an 'id' for the shared concept in another system (e.g. a CURIE). + * The 'logical' identifier of the Entity in the system of record, e.g. a UUID. This 'id' is unique within a given system, but may or may not be globally unique outside the system. It is used within a system to reference an object from another. */ id?: string | null; /** @@ -293,62 +363,141 @@ export interface SequenceReference { */ type?: "SequenceReference"; /** - * A primary label for the entity. + * A primary name for the entity. */ - label?: string | null; + name?: string | null; /** - * A free-text description of the entity. + * A free-text description of the Entity. */ description?: string | null; /** * Alternative name(s) for the Entity. */ - alternativeLabels?: string[] | null; + aliases?: string[] | null; /** - * A list of extensions to the entity. Extensions are not expected to be natively understood, but may be used for pre-negotiated exchange of message attributes between systems. + * A list of extensions to the Entity, that allow for capture of information not directly supported by elements defined in the model. */ extensions?: Extension[] | null; /** - * A list of mappings to concepts in terminologies or code systems. Each mapping should include a coding and a relation. - */ - mappings?: ConceptMapping[] | null; - /** - * A `GA4GH RefGet ` identifier for the referenced sequence, using the sha512t24u digest. + * A [GA4GH RefGet](http://samtools.github.io/hts-specs/refget.html) identifier for the referenced sequence, using the sha512t24u digest. */ refgetAccession: string; /** - * The interpretation of the character codes referred to by the refget accession, where 'aa' specifies an amino acid character set, and 'na' specifies a nucleic acid character set. + * The interpretation of the character codes referred to by the refget accession, where "aa" specifies an amino acid character set, and "na" specifies a nucleic acid character set. */ residueAlphabet?: ResidueAlphabet | null; /** * A boolean indicating whether the molecule represented by the sequence is circular (true) or linear (false). */ circular?: boolean | null; - [k: string]: unknown; + /** + * A sequenceString that is a literal representation of the referenced sequence. + */ + sequence?: SequenceString | null; + /** + * Molecule types as [defined by RefSeq](https://www.ncbi.nlm.nih.gov/books/NBK21091/) (see Table 1). MUST be one of 'genomic', 'RNA', 'mRNA', or 'protein'. + */ + moleculeType?: MoleculeType | null; } /** - * Define TranscriptSegment class + * Define TranscriptSegmentElement class */ export interface TranscriptSegmentElement { type?: "TranscriptSegmentElement"; transcript: string; + transcriptStatus: TranscriptPriority; + strand: Strand; exonStart?: number | null; exonStartOffset?: number | null; exonEnd?: number | null; exonEndOffset?: number | null; - gene: Gene; + gene: MappableConcept; elementGenomicStart?: SequenceLocation | null; elementGenomicEnd?: SequenceLocation | null; + coverage?: BreakpointCoverage | null; + anchoredReads?: AnchoredReads | null; +} +/** + * A `Location` defined by an interval on a `Sequence`. + */ +export interface SequenceLocation { + /** + * The 'logical' identifier of the Entity in the system of record, e.g. a UUID. This 'id' is unique within a given system, but may or may not be globally unique outside the system. It is used within a system to reference an object from another. + */ + id?: string | null; + /** + * MUST be "SequenceLocation" + */ + type?: "SequenceLocation"; + /** + * A primary name for the entity. + */ + name?: string | null; + /** + * A free-text description of the Entity. + */ + description?: string | null; + /** + * Alternative name(s) for the Entity. + */ + aliases?: string[] | null; + /** + * A list of extensions to the Entity, that allow for capture of information not directly supported by elements defined in the model. + */ + extensions?: Extension[] | null; + /** + * A sha512t24u digest created using the VRS Computed Identifier algorithm. + */ + digest?: string | null; + /** + * A reference to a SequenceReference on which the location is defined. + */ + sequenceReference?: IriReference | SequenceReference | null; + /** + * The start coordinate or range of the SequenceLocation. The minimum value of this coordinate or range is 0. For locations on linear sequences, this MUST represent a coordinate or range less than or equal to the value of `end`. For circular sequences, `start` is greater than `end` when the location spans the sequence 0 coordinate. + */ + start?: Range | number | null; + /** + * The end coordinate or range of the SequenceLocation. The minimum value of this coordinate or range is 0. For locations on linear sequences, this MUST represent a coordinate or range greater than or equal to the value of `start`. For circular sequences, `end` is less than `start` when the location spans the sequence 0 coordinate. + */ + end?: Range | number | null; + /** + * The literal sequence encoded by the `sequenceReference` at these coordinates. + */ + sequence?: SequenceString | null; +} +/** + * Define BreakpointCoverage class. + * + * This class models breakpoint coverage, or the number of fragments + * that are retained near the breakpoint for a fusion partner + */ +export interface BreakpointCoverage { + type?: "BreakpointCoverage"; + fragmentCoverage: number; + [k: string]: unknown; +} +/** + * Define AnchoredReads class + * + * This class can be used to report the number of reads that span the + * fusion junction. This is used at the TranscriptSegment level, as it + * indicates the transcript where the longer segment of the read is found + */ +export interface AnchoredReads { + type?: "AnchoredReads"; + reads: number; + [k: string]: unknown; } /** * Define Gene Element class. */ export interface GeneElement { type?: "GeneElement"; - gene: Gene; + gene: MappableConcept; } /** - * Define Templated Sequence Element class. + * Define TemplatedSequenceElement class. * * A templated sequence is a contiguous genomic sequence found in the gene * product. @@ -359,7 +508,7 @@ export interface TemplatedSequenceElement { strand: Strand; } /** - * Define Linker class (linker sequence) + * Define LinkerElement class (linker sequence) */ export interface LinkerElement { type?: "LinkerSequenceElement"; @@ -370,7 +519,7 @@ export interface LinkerElement { */ export interface LiteralSequenceExpression { /** - * The 'logical' identifier of the entity in the system of record, e.g. a UUID. This 'id' is unique within a given system. The identified entity may have a different 'id' in a different system, or may refer to an 'id' for the shared concept in another system (e.g. a CURIE). + * The 'logical' identifier of the Entity in the system of record, e.g. a UUID. This 'id' is unique within a given system, but may or may not be globally unique outside the system. It is used within a system to reference an object from another. */ id?: string | null; /** @@ -378,30 +527,22 @@ export interface LiteralSequenceExpression { */ type?: "LiteralSequenceExpression"; /** - * A primary label for the entity. + * A primary name for the entity. */ - label?: string | null; + name?: string | null; /** - * A free-text description of the entity. + * A free-text description of the Entity. */ description?: string | null; /** * Alternative name(s) for the Entity. */ - alternativeLabels?: string[] | null; + aliases?: string[] | null; /** - * A list of extensions to the entity. Extensions are not expected to be natively understood, but may be used for pre-negotiated exchange of message attributes between systems. + * A list of extensions to the Entity, that allow for capture of information not directly supported by elements defined in the model. */ extensions?: Extension[] | null; - /** - * A list of mappings to concepts in terminologies or code systems. Each mapping should include a coding and a relation. - */ - mappings?: ConceptMapping[] | null; - /** - * the literal sequence - */ - sequence: SequenceString; - [k: string]: unknown; + sequence: SequenceString1; } /** * Define UnknownGene class. @@ -417,6 +558,51 @@ export interface LiteralSequenceExpression { export interface UnknownGeneElement { type?: "UnknownGeneElement"; } +/** + * Define ContigSequence class. + * + * This class models the assembled contig sequence that supports the reported fusion + * event + */ +export interface ContigSequence { + type?: "ContigSequence"; + contig: string; + [k: string]: unknown; +} +/** + * Define ReadData class. + * + * This class is used at the AssayedFusion level when a fusion caller reports + * metadata describing sequencing reads for the fusion event + */ +export interface ReadData { + type?: "ReadData"; + split?: SplitReads | null; + spanning?: SpanningReads | null; + [k: string]: unknown; +} +/** + * Define SplitReads class. + * + * This class models the number of reads that cover the junction bewteen the + * detected partners in the fusion + */ +export interface SplitReads { + type?: "SplitReads"; + splitReads: number; + [k: string]: unknown; +} +/** + * Define SpanningReads class. + * + * This class models the number of pairs of reads that support the reported fusion + * event + */ +export interface SpanningReads { + type?: "SpanningReads"; + spanningReads: number; + [k: string]: unknown; +} /** * Define causative event information for a fusion. * @@ -454,7 +640,6 @@ export interface DomainParams { * biomedical literature for use in genomic knowledgebases. */ export interface CategoricalFusion { - type?: "CategoricalFusion"; regulatoryElement?: RegulatoryElement | null; structure: ( | TranscriptSegmentElement @@ -463,8 +648,13 @@ export interface CategoricalFusion { | LinkerElement | MultiplePossibleGenesElement )[]; + fivePrimeJunction?: string | null; + threePrimeJunction?: string | null; readingFramePreserved?: boolean | null; + type?: "CategoricalFusion"; + viccNomenclature?: string | null; criticalFunctionalDomains?: FunctionalDomain[] | null; + extensions?: Extension[] | null; } /** * Define MultiplePossibleGenesElement class. @@ -487,7 +677,7 @@ export interface MultiplePossibleGenesElement { export interface FunctionalDomain { type?: "FunctionalDomain"; status: DomainStatus; - associatedGene: Gene; + associatedGene: MappableConcept; id: string | null; label?: string | null; sequenceLocation?: SequenceLocation | null; @@ -497,7 +687,6 @@ export interface FunctionalDomain { * global FusionContext. */ export interface ClientAssayedFusion { - type?: "AssayedFusion"; regulatoryElement?: ClientRegulatoryElement | null; structure: ( | ClientTranscriptSegmentElement @@ -506,9 +695,15 @@ export interface ClientAssayedFusion { | ClientLinkerElement | ClientUnknownGeneElement )[]; + fivePrimeJunction?: string | null; + threePrimeJunction?: string | null; readingFramePreserved?: boolean | null; + type?: "AssayedFusion"; + viccNomenclature?: string | null; causativeEvent?: CausativeEvent | null; assay?: Assay | null; + contig?: ContigSequence | null; + readData?: ReadData | null; } /** * Define regulatory element object used client-side. @@ -519,8 +714,8 @@ export interface ClientRegulatoryElement { type?: "RegulatoryElement"; regulatoryClass: RegulatoryClass; featureId?: string | null; - associatedGene?: Gene | null; - featureLocation?: SequenceLocation | null; + associatedGene?: MappableConcept | null; + featureLocation?: GenomicLocation | null; displayClass: string; } /** @@ -531,13 +726,17 @@ export interface ClientTranscriptSegmentElement { nomenclature: string; type?: "TranscriptSegmentElement"; transcript: string; + transcriptStatus: TranscriptPriority; + strand: Strand; exonStart?: number | null; exonStartOffset?: number | null; exonEnd?: number | null; exonEndOffset?: number | null; - gene: Gene; + gene: MappableConcept; elementGenomicStart?: SequenceLocation | null; elementGenomicEnd?: SequenceLocation | null; + coverage?: BreakpointCoverage | null; + anchoredReads?: AnchoredReads | null; inputType: "genomic_coords" | "exon_coords"; inputTx?: string | null; inputStrand?: Strand | null; @@ -557,7 +756,7 @@ export interface ClientGeneElement { elementId: string; nomenclature: string; type?: "GeneElement"; - gene: Gene; + gene: MappableConcept; } /** * Templated sequence element used client-side. @@ -594,7 +793,6 @@ export interface ClientUnknownGeneElement { * global FusionContext. */ export interface ClientCategoricalFusion { - type?: "CategoricalFusion"; regulatoryElement?: ClientRegulatoryElement | null; structure: ( | ClientTranscriptSegmentElement @@ -603,8 +801,13 @@ export interface ClientCategoricalFusion { | ClientLinkerElement | ClientMultiplePossibleGenesElement )[]; + fivePrimeJunction?: string | null; + threePrimeJunction?: string | null; readingFramePreserved?: boolean | null; + type?: "CategoricalFusion"; + viccNomenclature?: string | null; criticalFunctionalDomains: ClientFunctionalDomain[] | null; + extensions?: Extension[] | null; } /** * Multiple possible gene element used client-side. @@ -620,7 +823,7 @@ export interface ClientMultiplePossibleGenesElement { export interface ClientFunctionalDomain { type?: "FunctionalDomain"; status: DomainStatus; - associatedGene: Gene; + associatedGene: MappableConcept; id: string | null; label?: string | null; sequenceLocation?: SequenceLocation | null; @@ -645,7 +848,7 @@ export interface CoordsUtilsResponse { */ export interface GenomicTxSegService { /** - * HGNC gene symbol. + * Valid, case-sensitive HGNC gene symbol. */ gene?: string | null; /** @@ -656,6 +859,14 @@ export interface GenomicTxSegService { * RefSeq transcript accession. */ tx_ac?: string | null; + /** + * Transcript priority for RefSeq transcript accession + */ + tx_status?: TranscriptPriority | null; + /** + * The strand that the transcript exists on. + */ + strand?: Strand | null; /** * Start transcript segment. */ @@ -668,9 +879,6 @@ export interface GenomicTxSegService { * Error messages. */ errors?: string[]; - /** - * Service metadata. - */ service_meta: ServiceMeta; } /** @@ -685,13 +893,59 @@ export interface TxSegment { * The value added to or subtracted from the `genomic_location` to find the start or end of an exon. */ offset?: number; + genomic_location: SequenceLocation1; +} +/** + * A `Location` defined by an interval on a `Sequence`. + */ +export interface SequenceLocation1 { + /** + * The 'logical' identifier of the Entity in the system of record, e.g. a UUID. This 'id' is unique within a given system, but may or may not be globally unique outside the system. It is used within a system to reference an object from another. + */ + id?: string | null; + /** + * MUST be "SequenceLocation" + */ + type?: "SequenceLocation"; + /** + * A primary name for the entity. + */ + name?: string | null; /** - * The genomic position of a transcript segment. + * A free-text description of the Entity. */ - genomic_location: SequenceLocation; + description?: string | null; + /** + * Alternative name(s) for the Entity. + */ + aliases?: string[] | null; + /** + * A list of extensions to the Entity, that allow for capture of information not directly supported by elements defined in the model. + */ + extensions?: Extension[] | null; + /** + * A sha512t24u digest created using the VRS Computed Identifier algorithm. + */ + digest?: string | null; + /** + * A reference to a SequenceReference on which the location is defined. + */ + sequenceReference?: IriReference | SequenceReference | null; + /** + * The start coordinate or range of the SequenceLocation. The minimum value of this coordinate or range is 0. For locations on linear sequences, this MUST represent a coordinate or range less than or equal to the value of `end`. For circular sequences, `start` is greater than `end` when the location spans the sequence 0 coordinate. + */ + start?: Range | number | null; + /** + * The end coordinate or range of the SequenceLocation. The minimum value of this coordinate or range is 0. For locations on linear sequences, this MUST represent a coordinate or range greater than or equal to the value of `start`. For circular sequences, `end` is less than `start` when the location spans the sequence 0 coordinate. + */ + end?: Range | number | null; + /** + * The literal sequence encoded by the `sequenceReference` at these coordinates. + */ + sequence?: SequenceString | null; } /** - * Metadata for cool_seq_tool service + * Service metadata. */ export interface ServiceMeta { name?: "cool_seq_tool"; @@ -723,13 +977,15 @@ export interface ExonCoordsRequest { * but the assayed_fusion and categorical_fusion constructors expect snake_case */ export interface FormattedAssayedFusion { - fusion_type?: AssayedFusion & string; + fusion_type?: AssayedFusion1; structure: ( | TranscriptSegmentElement | GeneElement | TemplatedSequenceElement | LinkerElement | UnknownGeneElement + | ContigSequence + | ReadData )[]; causative_event?: CausativeEvent | null; assay?: Assay | null; @@ -742,7 +998,7 @@ export interface FormattedAssayedFusion { * but the assayed_fusion and categorical_fusion constructors expect snake_case */ export interface FormattedCategoricalFusion { - fusion_type?: CategoricalFusion & string; + fusion_type?: CategoricalFusion1; structure: ( | TranscriptSegmentElement | GeneElement @@ -823,7 +1079,7 @@ export interface NormalizeGeneResponse { */ export interface RegulatoryElementResponse { warnings?: string[] | null; - regulatoryElement: RegulatoryElement; + regulatoryElement: RegulatoryElement | null; } /** * Abstract Response class for defining API response structures. diff --git a/client/src/services/main.tsx b/client/src/services/main.tsx index e990be75..3ea5296e 100644 --- a/client/src/services/main.tsx +++ b/client/src/services/main.tsx @@ -416,15 +416,32 @@ export const getFusionNomenclature = async ( * Build complete RegulatoryElement * @param regulatoryClass value of regulatory element class (the generated Typescript type expects it to be lowercase, which is fine -- the server will upper-case it) * @param geneName user-provided gene referent (could theoretically be some sort of concept ID or xref, too) + * @param featureId A identifier for the regulatory feature + * @param sequenceId RefSeq accession (NC_) + * @param start Genomic start location (residue) + * @param end Genomic end location (residue) * @returns constructed Regulatory element or warnings */ export const getRegulatoryElement = async ( regulatoryClass: RegulatoryClass, - geneName: string + geneName: string, + featureId: string, + sequenceId: string, + start: string, + end: string ): Promise => { - const response = await fetch( - `/api/construct/regulatory_element?element_class=${regulatoryClass}&gene_name=${geneName}` - ); + const params: Array = [ + `element_class=${regulatoryClass}`, + `gene_name=${geneName}`, + ]; + + if (featureId !== "") params.push(`feature_id=${featureId}`); + if (sequenceId !== "") params.push(`sequence_id=${sequenceId}`); + if (start !== "") params.push(`start=${start}`); + if (end !== "") params.push(`end=${end}`); + + const url = "/api/construct/regulatory_element?" + params.join("&"); + const response = await fetch(url); const responseJson = await response.json(); return responseJson; }; diff --git a/requirements.txt b/requirements.txt index 7ac3ee85..7d38f49f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,77 +1,62 @@ +# This file was autogenerated by uv via the following command: +# uv pip compile pyproject.toml -o ../requirements.txt --no-annotate agct==0.1.0.dev2 -aiofiles==24.1.0 annotated-types==0.7.0 anyio==4.4.0 asttokens==2.4.1 -async-timeout==4.0.3 asyncpg==0.29.0 attrs==24.2.0 -biocommons.seqrepo==0.6.9 +backports-datetime-fromisoformat==2.0.3 +biocommons-seqrepo==0.6.9 bioutils==0.5.8.post1 -black==24.8.0 boto3==1.35.3 botocore==1.35.3 canonicaljson==2.0.0 certifi==2024.7.4 -cfgv==3.4.0 charset-normalizer==3.3.2 +civicpy==5.0.0 click==8.1.7 coloredlogs==15.0.1 -configparser==7.1.0 -cool_seq_tool==0.7.1 -coverage==7.6.1 +cool-seq-tool==0.15.0 decorator==5.1.1 -distlib==0.3.8 +deprecation==2.1.0 executing==2.0.1 fastapi==0.112.1 -filelock==3.15.4 -fusor==0.4.4 -ga4gh.vrs==2.0.0a10 -gene-normalizer==0.4.0 +fusor==0.9.2 +ga4gh-cat-vrs==0.6.0 +ga4gh-va-spec==0.3.0 +ga4gh-vrs==2.1.4 +gene-normalizer==0.10.3 h11==0.14.0 -hgvs==1.5.4 -httpcore==1.0.5 -httpx==0.27.0 humanfriendly==10.0 -identify==2.6.0 idna==3.7 -importlib_metadata==8.4.0 -iniconfig==2.0.0 +importlib-metadata==8.4.0 ipython==8.26.0 jedi==0.19.1 -Jinja2==3.1.4 +jinja2==3.1.4 jmespath==1.0.1 -MarkupSafe==2.1.5 +markupsafe==2.1.5 matplotlib-inline==0.1.7 -mypy-extensions==1.0.0 -nodeenv==1.9.1 +networkx==3.5 +numpy==2.3.3 +obonet==1.1.1 packaging==24.1 -Parsley==1.3 +pandas==2.3.2 parso==0.8.4 -pathspec==0.12.1 pexpect==4.9.0 -platformdirs==4.2.2 -pluggy==1.5.0 polars==1.5.0 -pre-commit==3.8.0 -prompt_toolkit==3.0.47 -psycopg2==2.9.9 -psycopg2-binary==2.9.9 +prompt-toolkit==3.0.47 ptyprocess==0.7.0 -pure_eval==0.2.3 +pure-eval==0.2.3 pydantic==2.4.2 -pydantic-to-typescript2==1.0.4 -pydantic_core==2.10.1 -Pygments==2.18.0 +pydantic-core==2.10.1 +pygments==2.18.0 pysam==0.22.1 -pytest==8.3.2 -pytest-asyncio==0.24.0 -pytest-cov==5.0.0 python-dateutil==2.9.0.post0 -PyYAML==6.0.2 +pytz==2025.2 requests==2.32.3 -ruff==0.5.0 s3transfer==0.10.2 +setuptools==80.9.0 six==1.16.0 sniffio==1.3.1 sqlparse==0.5.1 @@ -80,11 +65,12 @@ starlette==0.38.2 tabulate==0.9.0 tqdm==4.66.5 traitlets==5.14.3 -typing_extensions==4.12.2 +typing-extensions==4.12.2 +tzdata==2025.2 urllib3==1.26.19 uvicorn==0.30.6 -virtualenv==20.26.3 -wags_tails==0.1.4 +vcfpy==0.13.8 +wags-tails==0.4.0 wcwidth==0.2.13 yoyo-migrations==8.2.0 zipp==3.20.0 diff --git a/server/pyproject.toml b/server/pyproject.toml index b81e1a00..255158f6 100644 --- a/server/pyproject.toml +++ b/server/pyproject.toml @@ -15,11 +15,11 @@ classifiers = [ "Topic :: Scientific/Engineering :: Bio-Informatics", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", ] -requires-python = ">=3.10" +requires-python = ">=3.11" description = "Curation tool for gene fusions" dependencies = [ "fastapi >= 0.72.0", @@ -28,10 +28,10 @@ dependencies = [ "click", "boto3", "botocore", - "fusor ~= 0.4.4", - "cool-seq-tool ~= 0.7.1", - "pydantic == 2.4.2", # validation errors with more recent versions, so don't remove this specific pin - "gene-normalizer ~= 0.4.0", + "fusor ~= 0.9.2", + "cool-seq-tool ~= 0.15.0", + "pydantic < 3, >= 2", + "gene-normalizer ~= 0.10.0", ] dynamic = ["version"] @@ -66,6 +66,9 @@ where = ["src"] root = "../." [tool.pytest.ini_options] +asyncio_mode = "auto" +asyncio_default_fixture_loop_scope = "session" +asyncio_default_test_loop_scope = "session" addopts = "--cov=src --cov-report term-missing" testpaths = ["tests"] diff --git a/server/src/curfu/devtools/build_gene_suggest.py b/server/src/curfu/devtools/build_gene_suggest.py index 54e8e581..1da2af06 100644 --- a/server/src/curfu/devtools/build_gene_suggest.py +++ b/server/src/curfu/devtools/build_gene_suggest.py @@ -120,7 +120,7 @@ def _save_suggest_file(self, output_dir: Path) -> None: "strand", ] today = datetime.datetime.strftime( - datetime.datetime.now(tz=datetime.timezone.utc), "%Y%m%d" + datetime.datetime.now(tz=datetime.UTC), "%Y%m%d" ) with (output_dir / f"gene_suggest_{today}.csv").open("w") as csvfile: writer = csv.DictWriter(csvfile, fieldnames=fieldnames) diff --git a/server/src/curfu/devtools/build_interpro.py b/server/src/curfu/devtools/build_interpro.py index 5f75a96d..cf4b0811 100644 --- a/server/src/curfu/devtools/build_interpro.py +++ b/server/src/curfu/devtools/build_interpro.py @@ -9,7 +9,7 @@ from timeit import default_timer as timer import click -from gene.database import create_db +from gene.database.dynamodb import DynamoDbDatabase from gene.query import QueryHandler from curfu import APP_ROOT, logger @@ -40,9 +40,7 @@ def download_protein2ipr(output_dir: Path) -> None: lambda data: fp.write(data), ) - today = datetime.datetime.strftime( - datetime.datetime.now(tz=datetime.timezone.utc), DATE_FMT - ) + today = datetime.datetime.strftime(datetime.datetime.now(tz=datetime.UTC), DATE_FMT) outfile_path = output_dir / f"protein2ipr_{today}.dat" with outfile_path.open("wb") as f_out, gzip.open(gz_file_path, "rb") as f_in: shutil.copyfileobj(f_in, f_out) @@ -65,7 +63,7 @@ def get_uniprot_refs() -> UniprotRefs: start = timer() # scanning on DynamoDB_Local is extremely slow - q = QueryHandler(create_db()) # must be dynamodb + q = QueryHandler(DynamoDbDatabase()) # must be dynamodb genes = q.db.genes uniprot_ids: UniprotRefs = {} @@ -86,7 +84,7 @@ def get_uniprot_refs() -> UniprotRefs: continue norm_response = q.normalize(uniprot_id) norm_id = norm_response.gene.gene_id - norm_label = norm_response.gene.label + norm_label = norm_response.gene.name uniprot_ids[uniprot_id] = (norm_id, norm_label) if not last_evaluated_key: break @@ -96,9 +94,7 @@ def get_uniprot_refs() -> UniprotRefs: logger.info(msg) click.echo(msg) - today = datetime.datetime.strftime( - datetime.datetime.now(tz=datetime.timezone.utc), DATE_FMT - ) + today = datetime.datetime.strftime(datetime.datetime.now(tz=datetime.UTC), DATE_FMT) save_path = APP_ROOT / "data" / f"uniprot_refs_{today}.tsv" with save_path.open("w") as out: for uniprot_ref, data in uniprot_ids.items(): @@ -121,9 +117,7 @@ def download_uniprot_sprot(output_dir: Path) -> Path: "uniprot_sprot.xml.gz", lambda data: fp.write(data), ) - today = datetime.datetime.strftime( - datetime.datetime.now(tz=datetime.timezone.utc), DATE_FMT - ) + today = datetime.datetime.strftime(datetime.datetime.now(tz=datetime.UTC), DATE_FMT) outfile_path = output_dir / f"uniprot_sprot_{today}.dat" with outfile_path.open("wb") as f_out, gzip.open(gz_file_path, "rb") as f_in: shutil.copyfileobj(f_in, f_out) @@ -153,7 +147,7 @@ def get_interpro_uniprot_rels( if not protein_ipr_path: download_protein2ipr(output_dir) today = datetime.datetime.strftime( - datetime.datetime.now(tz=datetime.timezone.utc), DATE_FMT + datetime.datetime.now(tz=datetime.UTC), DATE_FMT ) protein_ipr_path = output_dir / f"protein2ipr_{today}.dat" protein_ipr = protein_ipr_path.open() @@ -287,7 +281,7 @@ def build_gene_domain_maps( directory. """ start_time = timer() - today = datetime.strftime(datetime.datetime.now(tz=datetime.timezone.utc), DATE_FMT) + today = datetime.datetime.strftime(datetime.datetime.now(tz=datetime.UTC), DATE_FMT) # get relevant Interpro IDs interpro_data_bin = [] diff --git a/server/src/curfu/gene_services.py b/server/src/curfu/gene_services.py index f6d7ee6d..17c60a57 100644 --- a/server/src/curfu/gene_services.py +++ b/server/src/curfu/gene_services.py @@ -59,13 +59,13 @@ def get_normalized_gene( """ response = normalizer.normalize(term) if response.match_type != MatchType.NO_MATCH: - concept_id = response.normalized_id gene = response.gene + concept_id = gene.id.split("normalize.gene.")[-1] if not concept_id or not response.gene: msg = f"Unexpected null property in normalized response for `{term}`" logger.error(msg) raise LookupServiceError(msg) - symbol = gene.label + symbol = gene.name if not symbol: msg = f"Unable to retrieve symbol for gene {concept_id}" logger.error(msg) @@ -106,7 +106,7 @@ def get_normalized_gene( break if not term_cased: logger.warning( - f"Couldn't find cased version for search term {term} matching gene ID {response.normalized_id}" + f"Couldn't find cased version for search term {term} matching gene ID {concept_id}" ) return (concept_id, symbol, term_cased) warn = f"Lookup of gene term {term} failed." diff --git a/server/src/curfu/routers/constructors.py b/server/src/curfu/routers/constructors.py index 55260dc1..0b415d71 100644 --- a/server/src/curfu/routers/constructors.py +++ b/server/src/curfu/routers/constructors.py @@ -1,5 +1,6 @@ """Provide routes for element construction endpoints""" +from cool_seq_tool.schemas import CoordinateType from fastapi import APIRouter, Query, Request from fusor.models import DomainStatus, RegulatoryClass from pydantic import ValidationError @@ -115,7 +116,6 @@ async def build_tx_segment_gc( seg_start_genomic=start, seg_end_genomic=end, transcript=transcript, - get_nearest_transcript_junction=True, ) return TxSegmentElementResponse(element=tx_segment, warnings=warnings) @@ -134,8 +134,8 @@ def build_templated_sequence_element( \f :param request: the HTTP request context, supplied by FastAPI. Use to access FUSOR and UTA-associated tools. - :param start: genomic starting position - :param end: genomic ending position + :param start: genomic starting position (residue) + :param end: genomic ending position (residue) :param sequence_id: chromosome accession for sequence :param strand: chromosome strand - must be one of {'+', '-'} :return: Pydantic class with Templated Sequnce element if successful, or warnings @@ -152,6 +152,7 @@ def build_templated_sequence_element( end=end, sequence_id=parse_identifier(sequence_id), strand=strand_n, + coordinate_type=CoordinateType.RESIDUE, ) return TemplatedSequenceElementResponse(element=element, warnings=[]) @@ -191,7 +192,14 @@ def build_domain( response: ResponseDict = {} try: domain, warnings = request.app.state.fusor.functional_domain( - status, name, domain_id, gene_id, sequence_id, start, end + status, + name, + domain_id, + gene_id, + sequence_id, + start, + end, + coordinate_type=CoordinateType.RESIDUE, ) if warnings: response["warnings"] = [warnings] @@ -210,7 +218,13 @@ def build_domain( tags=[RouteTag.CONSTRUCTORS], ) def build_regulatory_element( - request: Request, element_class: RegulatoryClass, gene_name: str + request: Request, + element_class: RegulatoryClass, + gene_name: str, + feature_id: str | None = None, + sequence_id: str | None = None, + start: int | None = None, + end: int | None = None, ) -> ResponseDict: """Construct regulatory element from given params. \f @@ -218,13 +232,34 @@ def build_regulatory_element( FUSOR and UTA-associated tools. :param element_class: type of regulatory element :param gene_name: referent acquired from autocomplete. + :param feature_id: The feature ID for the regulatory element + :param sequence_id: chromosome RefSeq accession for sequence + :param start: Genomic start position (residue) + :param end: Genomic end position (residue) :return: complete regulatory element object or warning message """ + response: ResponseDict = {"warnings": None, "regulatoryElement": None} try: normalized_class = RegulatoryClass[element_class.upper()] except KeyError: - return {"warnings": [f"unrecognized regulatory class value: {element_class}"]} - element, warnings = request.app.state.fusor.regulatory_element( - normalized_class, gene_name - ) - return {"regulatoryElement": element, "warnings": warnings} + response["warnings"] = [f"unrecognized regulatory class value: {element_class}"] + return response + + try: + element, warnings = request.app.state.fusor.regulatory_element( + normalized_class, + gene_name, + feature_id=feature_id, + sequence_id=sequence_id, + start=start, + end=end, + coordinate_type=CoordinateType.RESIDUE, + ) + if warnings: + response["warnings"] = [warnings] + else: + response["regulatoryElement"] = element + except ValidationError as e: + response["warnings"] = [f"Unable to construct Regulatory Element: {e}"] + + return response diff --git a/server/src/curfu/routers/demo.py b/server/src/curfu/routers/demo.py index 01f791f6..a433f085 100644 --- a/server/src/curfu/routers/demo.py +++ b/server/src/curfu/routers/demo.py @@ -106,7 +106,7 @@ def clientify_structural_element( element_args["inputExonStartOffset"] = str(element.exonStartOffset) element_args["inputExonEnd"] = str(element.exonEnd) element_args["inputExonEndOffset"] = str(element.exonEndOffset) - element_args["inputGene"] = element.gene.label + element_args["inputGene"] = element.gene.name return ClientTranscriptSegmentElement(**element_args) msg = "Unknown element type provided" raise ValueError(msg) diff --git a/server/src/curfu/routers/lookup.py b/server/src/curfu/routers/lookup.py index 33d97274..5b543dbf 100644 --- a/server/src/curfu/routers/lookup.py +++ b/server/src/curfu/routers/lookup.py @@ -59,7 +59,7 @@ async def get_transcripts_for_gene(request: Request, gene: str) -> dict: :return: Dict containing transcripts if lookup succeeds, or warnings upon failure """ normalized = request.app.state.fusor.gene_normalizer.normalize(gene) - symbol = normalized.gene.label + symbol = normalized.gene.name transcripts = await request.app.state.fusor.cool_seq_tool.uta_db.get_transcripts( gene=symbol ) diff --git a/server/src/curfu/routers/utilities.py b/server/src/curfu/routers/utilities.py index 1120e74d..b5d48029 100644 --- a/server/src/curfu/routers/utilities.py +++ b/server/src/curfu/routers/utilities.py @@ -4,6 +4,7 @@ from pathlib import Path from typing import Any +from cool_seq_tool.schemas import CoordinateType from fastapi import APIRouter, HTTPException, Query, Request from fastapi.responses import FileResponse from gene import schemas as gene_schemas @@ -38,14 +39,14 @@ def get_mane_transcripts(request: Request, term: str) -> dict: normalized = request.app.state.fusor.gene_normalizer.normalize(term) if normalized.match_type == gene_schemas.MatchType.NO_MATCH: return {"warnings": [f"Normalization error: {term}"], "transcripts": None} - if not normalized.normalized_id.startswith("hgnc"): + if not normalized.gene.id.startswith("normalize.gene.hgnc"): return {"warnings": [f"No HGNC symbol: {term}"], "transcripts": None} - symbol = normalized.gene.label + symbol = normalized.gene.name transcripts = request.app.state.fusor.cool_seq_tool.mane_transcript_mappings.get_gene_mane_data( symbol ) if not transcripts: - return {"warnings": [f"No matching transcripts: {term}"]} + return {"warnings": [f"No matching transcripts: {term}"], "transcripts": None} return {"transcripts": transcripts} @@ -163,7 +164,7 @@ async def get_exon_coords( seg_end_genomic=end, transcript=transcript, gene=gene, - get_nearest_transcript_junction=True, + coordinate_type=CoordinateType.RESIDUE, ) warnings = response.errors if warnings: diff --git a/server/src/curfu/schemas.py b/server/src/curfu/schemas.py index 43f92051..761cdec3 100644 --- a/server/src/curfu/schemas.py +++ b/server/src/curfu/schemas.py @@ -36,7 +36,13 @@ ResponseDict = dict[ str, - str | int | list[str] | list[tuple[str, str, str, str]] | FunctionalDomain | None, + str + | int + | list[str] + | list[tuple[str, str, str, str]] + | FunctionalDomain + | RegulatoryElement + | None, ] Warnings = list[str] @@ -230,9 +236,9 @@ class ManeGeneTranscript(BaseModel): symbol: str name: str RefSeq_nuc: str - RefSeq_prot: str + RefSeq_prot: str | None Ensembl_nuc: str - Ensembl_prot: str + Ensembl_prot: str | None MANE_status: str GRCh38_chr: str chr_start: int @@ -332,7 +338,7 @@ class NomenclatureResponse(Response): class RegulatoryElementResponse(Response): """Response model for regulatory element constructor.""" - regulatoryElement: RegulatoryElement + regulatoryElement: RegulatoryElement | None class DemoResponse(Response): diff --git a/server/src/curfu/sequence_services.py b/server/src/curfu/sequence_services.py index eea3d12e..49ad7932 100644 --- a/server/src/curfu/sequence_services.py +++ b/server/src/curfu/sequence_services.py @@ -23,4 +23,8 @@ def get_strand(strand_input: str) -> int: return Strand.POSITIVE if strand_input == "-": return Strand.NEGATIVE - raise InvalidInputError + + try: + return Strand(strand_input) + except ValueError as e: + raise InvalidInputError from e diff --git a/server/tests/conftest.py b/server/tests/conftest.py index 3f9e95a2..9fe02e51 100644 --- a/server/tests/conftest.py +++ b/server/tests/conftest.py @@ -87,9 +87,13 @@ def check_sequence_location(sequence_location, expected_sequence_location): def alk_gene(): """Gene object for ALK""" return { - "type": "Gene", - "label": "ALK", - "id": "hgnc:427", + "conceptType": "Gene", + "name": "ALK", + "primaryCoding": { + "id": "hgnc:427", + "code": "HGNC:427", + "system": "https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/", + }, } @@ -97,9 +101,13 @@ def alk_gene(): def tpm3_gene(): """Gene object for TPM3""" return { - "type": "Gene", - "label": "TPM3", - "id": "hgnc:12012", + "conceptType": "Gene", + "name": "TPM3", + "primaryCoding": { + "id": "hgnc:12012", + "code": "HGNC:12012", + "system": "https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/", + }, } @@ -107,9 +115,13 @@ def tpm3_gene(): def ntrk1_gene(): """Gene object for NTRK1""" return { - "type": "Gene", - "label": "NTRK1", - "id": "hgnc:8031", + "conceptType": "Gene", + "name": "NTRK1", + "primaryCoding": { + "id": "hgnc:8031", + "code": "HGNC:8031", + "system": "https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/", + }, } @@ -127,6 +139,7 @@ def ntrk1_tx_element_start(ntrk1_gene): return { "type": "TranscriptSegmentElement", "transcript": "refseq:NM_002529.3", + "transcriptStatus": "longest_compatible_remaining", "exonStart": 2, "exonStartOffset": 1, "gene": ntrk1_gene, @@ -135,6 +148,7 @@ def ntrk1_tx_element_start(ntrk1_gene): "type": "SequenceLocation", "start": 156864354, }, + "strand": 1, } @@ -187,4 +201,5 @@ def tpm3_tx_g_element(tpm3_gene): "type": "SequenceLocation", "start": 154171417, }, + "strand": -1, } diff --git a/server/tests/integration/test_constructors.py b/server/tests/integration/test_constructors.py index e26f5a94..d6e30dd8 100644 --- a/server/tests/integration/test_constructors.py +++ b/server/tests/integration/test_constructors.py @@ -16,9 +16,9 @@ def check_gene_element_response( assert response["element"]["type"] == expected_response["element"]["type"] response_gd = response["element"]["gene"] expected_gd = expected_response["element"]["gene"] - assert response_gd["id"] == expected_id - assert response_gd["type"] == expected_gd["type"] - assert response_gd["label"] == expected_gd["label"] + assert response_gd["primaryCoding"]["id"] == expected_id + assert response_gd["conceptType"] == expected_gd["conceptType"] + assert response_gd["name"] == expected_gd["name"] alk_gene_response = {"warnings": [], "element": alk_gene_element} @@ -149,7 +149,7 @@ async def test_build_tx_segment_ec( # test handle invalid transcript await check_response( "/api/construct/structural_element/tx_segment_ec?transcript=NM_0012529.3&exon_start=3", - {"warnings": ["No exons found given NM_0012529.3"]}, + {"warnings": ["Transcript does not exist in UTA: NM_0012529.3"]}, check_tx_element_response, ) @@ -162,7 +162,7 @@ async def test_build_segment_gc( genomic coordinates and gene name. """ await check_response( - "/api/construct/structural_element/tx_segment_gc?gene=TPM3&chromosome=NC_000001.11&start=154171416&end=154171417", + "/api/construct/structural_element/tx_segment_gc?gene=TPM3&chromosome=NC_000001.11&start=154171416&end=154171417&transcript=NM_152263.4", {"element": tpm3_tx_g_element}, check_tx_element_response, ) @@ -171,7 +171,7 @@ async def test_build_segment_gc( genomic coordinates and transcript. """ await check_response( - "/api/construct/structural_element/tx_segment_gc?transcript=NM_152263.4&chromosome=NC_000001.11&start=154171416&end=154171417", + "/api/construct/structural_element/tx_segment_gc?transcript=NM_152263.4&chromosome=NC_000001.11&start=154171416&end=154171417&gene=TPM3", {"element": tpm3_tx_t_element}, check_tx_element_response, ) @@ -185,9 +185,45 @@ async def test_build_reg_element(check_response, check_reg_element_response): { "regulatoryElement": { "associatedGene": { - "id": "hgnc:1097", - "label": "BRAF", - "type": "Gene", + "primaryCoding": { + "id": "hgnc:1097", + "code": "HGNC:1097", + "system": "https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/", + }, + "name": "BRAF", + "conceptType": "Gene", + }, + "regulatoryClass": "promoter", + "type": "RegulatoryElement", + } + }, + check_reg_element_response, + ) + + await check_response( + "/api/construct/regulatory_element?element_class=promoter&gene_name=braf&sequence_id=NC_000001.11&start=15456&end=15456", + { + "regulatoryElement": { + "associatedGene": { + "primaryCoding": { + "id": "hgnc:1097", + "code": "HGNC:1097", + "system": "https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/", + }, + "name": "BRAF", + "conceptType": "Gene", + }, + "featureLocation": { + "id": "ga4gh:SL.-xC3omZDIKZEuotbbHWQMTC8sS3nOxTb", + "name": "NC_000001.11", + "type": "SequenceLocation", + "sequenceReference": { + "id": "refseq:NC_000001.11", + "refgetAccession": "SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO", + "type": "SequenceReference", + }, + "start": 15455, + "end": 15456, }, "regulatoryClass": "promoter", "type": "RegulatoryElement", @@ -220,13 +256,13 @@ async def test_build_templated_sequence( }, } await check_response( - "/api/construct/structural_element/templated_sequence?start=154171415&end=154171417&sequence_id=NC_000001.11&strand=-", + "/api/construct/structural_element/templated_sequence?start=154171416&end=154171417&sequence_id=NC_000001.11&strand=-", expected, check_templated_sequence_response, ) await check_response( - "/api/construct/structural_element/templated_sequence?start=154171415&end=154171417&sequence_id=refseq%3ANC_000001.11&strand=-", + "/api/construct/structural_element/templated_sequence?start=154171416&end=154171417&sequence_id=refseq%3ANC_000001.11&strand=-", expected, check_templated_sequence_response, ) diff --git a/server/tests/integration/test_lookup.py b/server/tests/integration/test_lookup.py index 7600756e..336aef91 100644 --- a/server/tests/integration/test_lookup.py +++ b/server/tests/integration/test_lookup.py @@ -46,8 +46,6 @@ async def test_normalize_gene(async_client: AsyncClient): response = await async_client.get("/api/lookup/gene?term=sdfliuwer") assert response.status_code == 200 assert response.json() == { - "cased": "", - "symbol": "", "term": "sdfliuwer", "warnings": ["Lookup of gene term sdfliuwer failed."], }, "Failed lookup should still respond successfully" diff --git a/server/tests/integration/test_nomenclature.py b/server/tests/integration/test_nomenclature.py index 811e9cfe..63c45fa5 100644 --- a/server/tests/integration/test_nomenclature.py +++ b/server/tests/integration/test_nomenclature.py @@ -10,7 +10,15 @@ def regulatory_element(): """Provide regulatory element fixture.""" return { "regulatoryClass": "promoter", - "associatedGene": {"id": "hgnc:9339", "label": "G1", "type": "Gene"}, + "associatedGene": { + "name": "G1", + "conceptType": "Gene", + "primaryCoding": { + "id": "hgnc:9339", + "code": "HGNC:9339", + "system": "https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/", + }, + }, } @@ -20,23 +28,26 @@ def epcam_5_prime(): return { "type": "TranscriptSegmentElement", "transcript": "refseq:NM_002354.2", + "transcriptStatus": "longest_compatible_remaining", "exonEnd": 5, "exonEndOffset": 0, "gene": { - "type": "Gene", - "label": "EPCAM", - "id": "hgnc:11529", + "conceptType": "Gene", + "name": "EPCAM", + "primaryCoding": { + "id": "hgnc:11529", + "code": "HGNC:11529", + "system": "https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/", + }, }, "elementGenomicEnd": { "id": "fusor.location_descriptor:NC_000002.12", "type": "SequenceLocation", - "label": "NC_000002.12", - "location": { - "type": "SequenceLocation", - "start": 47377013, - "end": 47377014, - }, + "name": "NC_000002.12", + "start": 47377013, + "end": 47377014, }, + "strand": 1, } @@ -46,12 +57,17 @@ def epcam_3_prime(): return { "type": "TranscriptSegmentElement", "transcript": "refseq:NM_002354.2", + "transcriptStatus": "longest_compatible_remaining", "exonStart": 5, "exonStartOffset": 0, "gene": { - "type": "Gene", - "label": "EPCAM", - "id": "hgnc:11529", + "conceptType": "Gene", + "name": "EPCAM", + "primaryCoding": { + "id": "hgnc:11529", + "code": "HGNC:11529", + "system": "https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/", + }, }, "elementGenomicStart": { "id": "fusor.location_descriptor:NC_000002.12", @@ -59,6 +75,7 @@ def epcam_3_prime(): "start": 47377013, "end": 47377014, }, + "strand": 1, } @@ -70,9 +87,13 @@ def epcam_invalid(): "exonEnd": 5, "exonEndOffset": 0, "gene": { - "type": "Gene", - "label": "EPCAM", - "id": "hgnc:11529", + "conceptType": "Gene", + "name": "EPCAM", + "primaryCoding": { + "id": "hgnc:11529", + "code": "HGNC:11529", + "system": "https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/", + }, }, "elementGenomicEnd": { "id": "fusor.location_descriptor:NC_000002.12", @@ -80,6 +101,7 @@ def epcam_invalid(): "start": 47377013, "end": 47377014, }, + "strand": 1, } @@ -148,7 +170,7 @@ async def test_tx_segment_nomenclature( ) assert response.status_code == 200 expected_warnings = [ - "validation error for TranscriptSegmentElement", + "validation errors for TranscriptSegmentElement", "Field required", ] for expected in expected_warnings: @@ -203,7 +225,7 @@ async def test_templated_sequence_nomenclature( assert response.status_code == 200 expected_warnings = [ "validation error for TemplatedSequenceElement", - "Input should be a valid integer", + "Input should be 1 or -1", ] for expected in expected_warnings: assert expected in response.json().get("warnings", [])[0] diff --git a/server/tests/integration/test_utilities.py b/server/tests/integration/test_utilities.py index 085d024d..9a707f4f 100644 --- a/server/tests/integration/test_utilities.py +++ b/server/tests/integration/test_utilities.py @@ -116,6 +116,7 @@ def check_genomic_coords_response(response: dict, expected_response: dict): "refgetAccession": "SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO", }, "start": 156860878, + "extensions": [{"name": "is_exonic", "value": True}], }, }, "seg_end": { @@ -128,6 +129,7 @@ def check_genomic_coords_response(response: dict, expected_response: dict): "refgetAccession": "SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO", }, "end": 156868647, + "extensions": [{"name": "is_exonic", "value": True}], }, }, "errors": [], @@ -194,6 +196,7 @@ def check_coords_response(response: dict, expected_response: dict): "refgetAccession": "SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO", }, "end": 154192135, + "extensions": [{"name": "is_exonic", "value": True}], }, }, "errors": [], @@ -223,14 +226,15 @@ def check_coords_response(response: dict, expected_response: dict): "tx_ac": "NM_152263.3", "seg_end": { "exon_ord": 0, - "offset": 1, + "offset": 2, "genomic_location": { "type": "SequenceLocation", "sequenceReference": { "type": "SequenceReference", "refgetAccession": "SQ.Ya6Rs7DHhDeg7YaOSg1EoNi3U_nQ9SvO", }, - "start": 154191900, + "start": 154191899, + "extensions": [{"name": "is_exonic", "value": False}], }, }, "errors": [], diff --git a/server/tests/integration/test_validate.py b/server/tests/integration/test_validate.py index d31fabac..6ede0ddb 100644 --- a/server/tests/integration/test_validate.py +++ b/server/tests/integration/test_validate.py @@ -14,9 +14,13 @@ def alk_fusion(): { "type": "GeneElement", "gene": { - "id": "hgnc:427", - "type": "Gene", - "label": "ALK", + "conceptType": "Gene", + "name": "ALK", + "primaryCoding": { + "id": "hgnc:427", + "code": "HGNC:427", + "system": "https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/", + }, }, }, {"type": "MultiplePossibleGenesElement"}, @@ -28,9 +32,13 @@ def alk_fusion(): { "type": "GeneElement", "gene": { - "id": "hgnc:427", - "type": "Gene", - "label": "ALK", + "conceptType": "Gene", + "name": "ALK", + "primaryCoding": { + "id": "hgnc:427", + "code": "HGNC:427", + "system": "https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/", + }, }, }, {"type": "MultiplePossibleGenesElement"}, @@ -49,7 +57,15 @@ def ewsr1_fusion(): "structure": [ { "type": "GeneElement", - "gene": {"type": "Gene", "label": "EWSR1", "id": "hgnc:3508"}, + "gene": { + "conceptType": "Gene", + "name": "EWSR1", + "primaryCoding": { + "id": "hgnc:3508", + "code": "HGNC:3508", + "system": "https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/", + }, + }, }, {"type": "UnknownGeneElement"}, ], @@ -67,7 +83,15 @@ def ewsr1_fusion(): "structure": [ { "type": "GeneElement", - "gene": {"type": "Gene", "label": "EWSR1", "id": "hgnc:3508"}, + "gene": { + "conceptType": "Gene", + "name": "EWSR1", + "primaryCoding": { + "id": "hgnc:3508", + "code": "HGNC:3508", + "system": "https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/", + }, + }, }, {"type": "UnknownGeneElement"}, ], @@ -94,7 +118,15 @@ def ewsr1_fusion_fill_types(): "type": "AssayedFusion", "structure": [ { - "gene": {"type": "Gene", "label": "EWSR1", "id": "hgnc:3508"}, + "gene": { + "conceptType": "Gene", + "name": "EWSR1", + "primaryCoding": { + "id": "hgnc:3508", + "code": "HGNC:3508", + "system": "https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/", + }, + }, }, {"type": "UnknownGeneElement"}, ], @@ -111,7 +143,15 @@ def ewsr1_fusion_fill_types(): "structure": [ { "type": "GeneElement", - "gene": {"type": "Gene", "label": "EWSR1", "id": "hgnc:3508"}, + "gene": { + "conceptType": "Gene", + "name": "EWSR1", + "primaryCoding": { + "id": "hgnc:3508", + "code": "HGNC:3508", + "system": "https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/", + }, + }, }, {"type": "UnknownGeneElement"}, ], @@ -138,10 +178,14 @@ def wrong_type_fusion(): { "type": "GeneElement", "gene": { - "type": "Gene", + "conceptType": "Gene", "id": "normalize.gene:EWSR1", - "label": "EWSR1", - "gene_id": "hgnc:3508", + "name": "EWSR1", + "primaryCoding": { + "id": "hgnc:3508", + "code": "HGNC:3508", + "system": "https://www.genenames.org/data/gene-symbol-report/#!/hgnc_id/", + }, }, }, {"type": "UnknownGeneElement"},