Skip to content

Commit

Permalink
feat: implementation of ACMG CNV criteria
Browse files Browse the repository at this point in the history
  • Loading branch information
gromdimon committed Oct 10, 2023
1 parent 22b2001 commit 81696da
Show file tree
Hide file tree
Showing 8 changed files with 1,353 additions and 621 deletions.
27 changes: 27 additions & 0 deletions backend/app/api/internal/endpoints/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,30 @@ async def acmg(request: Request):
if key.lower() in acmg_rating:
acmg_rating[key.lower()] = value == 1
return JSONResponse(acmg_rating)


@router.get("/cnv/acmg/{path:path}")
async def cnv_acmg(request: Request):
"""Implement searching for ACMG classification for CNVs."""
query_params = request.query_params
chromosome = query_params.get("chromosome")
start = query_params.get("start")
end = query_params.get("end")
func = query_params.get("func")

if not chromosome or not start or not end or not func:
return Response(status_code=400, content="Missing query parameters")

url = "https://phoenix.bgi.com/api/acit/jobs/"
client = httpx.AsyncClient()
backend_req = client.build_request(
method="POST",
url=url,
data={"chromosome": chromosome, "start": start, "end": end, "func": func, "error": 0},
)
backend_resp = await client.send(backend_req)
if backend_resp.status_code != 200:
return Response(status_code=backend_resp.status_code, content=backend_resp.content)

print(backend_resp.json())
return JSONResponse(backend_resp.json())
218 changes: 218 additions & 0 deletions frontend/src/components/SvDetails/AcmgRating.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue'

Check warning on line 2 in frontend/src/components/SvDetails/AcmgRating.vue

View workflow job for this annotation

GitHub Actions / Frontend-Lint

'ref' is defined but never used
import {
ACMG_CRITERIA_CNV_DEFS,
ACMG_CRITERIA_CNV_GAIN,
ACMG_CRITERIA_CNV_LOSS,
AcmgCriteriaCNVGain,
AcmgCriteriaCNVLoss,
Presence,
StateSourceCNV
} from '@/lib/acmgCNV'
import { StoreState } from '@/stores/misc'
import { useSvAcmgRatingStore } from '@/stores/svAcmgRating'
import type { SvRecord } from '@/stores/svInfo'
const props = defineProps({
svRecord: Object as () => SvRecord | undefined
})
const acmgRatingStore = useSvAcmgRatingStore()
const resetAcmgRating = () => {
if (!acmgRatingStore.acmgRating) {
return
}
acmgRatingStore.acmgRating.setUserPresenceAutoCNV()
}
const calculateAcmgClass = computed((): string => {
if (!acmgRatingStore.acmgRating) {
return ''
}
const [acmgClass] = acmgRatingStore.acmgRating.getAcmgClass()
return acmgClass
})
const calculateAcmgScore = computed((): number => {
if (!acmgRatingStore.acmgRating) {
return 0
}
const [, score] = acmgRatingStore.acmgRating.getAcmgClass()
return score
})
watch(
() => [props.svRecord, acmgRatingStore.storeState],
async () => {
if (props.svRecord && acmgRatingStore.storeState === StoreState.Active) {
await acmgRatingStore.setAcmgRating(props.svRecord)
}
}
)
onMounted(async () => {
if (props.svRecord) {
await acmgRatingStore.setAcmgRating(props.svRecord)
}
})
const switchCriteria = (
criteria: AcmgCriteriaCNVLoss | AcmgCriteriaCNVGain,
presence: Presence
) => {
if (presence === Presence.Present) {
acmgRatingStore.acmgRating.setPresence(StateSourceCNV.User, criteria, Presence.Absent)
} else {
acmgRatingStore.acmgRating.setPresence(StateSourceCNV.User, criteria, Presence.Present)
console.log('setPresence, ', criteria, acmgRatingStore.acmgRating.getCriteriaCNVState(criteria))
}
}
</script>

<template>
<div v-if="acmgRatingStore !== undefined">
<v-row>
<v-col cols="12" md="3"></v-col>
<v-col cols="12" md="6" class="section">
<div>
<div>
<h2 for="acmg-class"><strong>ACMG classification:</strong></h2>
</div>
<h1 title="Automatically determined ACMG class (Richards et al., 2015)">
{{ calculateAcmgClass }} with score: {{ calculateAcmgScore }}
</h1>
<router-link to="/acmg-docs" target="_blank">
Further documentation <v-icon>mdi-open-in-new</v-icon>
</router-link>
</div>
<div class="button-group">
<v-btn color="black" variant="outlined" @click="resetAcmgRating()"> Reset </v-btn>
</div>
<div>
<div>
<div>
<v-icon>mdi-information-outline</v-icon>
Select all fulfilled criteria to get the classification following Rooney Riggs
<i>et al.</i> (2020).
</div>
</div>
</div>
</v-col>
<v-col cols="12" md="3"></v-col>
</v-row>
<v-row>
<v-col class="d-flex flex-row flex-wrap">
<v-table>
<thead>
<tr>
<th width="20%">Evidence</th>
<th width="74%">Description</th>
<th width="3%">Suggested points</th>
<th width="3%">Max score</th>
</tr>
</thead>
<tbody v-if="props.svRecord.svType === 'DEL'">
<tr v-for="criteria in ACMG_CRITERIA_CNV_LOSS" :key="criteria">
<td>
<v-switch
:label="criteria"
:model-value="
acmgRatingStore.acmgRating.getCriteriaCNVState(criteria).presence ===
Presence.Present
"
@update:model-value="
switchCriteria(
criteria,
acmgRatingStore.acmgRating.getCriteriaCNVState(criteria).presence
)
"
hide-details="auto"
density="compact"
class="switch"
>
</v-switch>
<div v-if="ACMG_CRITERIA_CNV_DEFS.get(criteria)?.slider">
<v-slider
:v-model="acmgRatingStore.acmgRating.getCriteriaCNVState(criteria).score"
:min="0"
:max="1"
:step="0.1"
thumb-label
thumb-size="10"
class="slider"
/>
</div>
</td>
<td>
{{ ACMG_CRITERIA_CNV_DEFS.get(criteria)?.hint }}
<br />
{{ ACMG_CRITERIA_CNV_DEFS.get(criteria)?.description ?? '' }}
<br />
Conflicting evidence:
{{ ACMG_CRITERIA_CNV_DEFS.get(criteria)?.conflictingEvidence }}
</td>
<td>{{ ACMG_CRITERIA_CNV_DEFS.get(criteria)?.defaultScore }}</td>
<td>{{ ACMG_CRITERIA_CNV_DEFS.get(criteria)?.maxScore }}</td>
</tr>
</tbody>
<tbody v-else-if="props.svRecord.svType === 'DUP'">
<tr v-for="criteria in ACMG_CRITERIA_CNV_GAIN" :key="criteria">
<td>
<v-switch
:label="criteria"
:model-value="
acmgRatingStore.acmgRating.getCriteriaCNVState(criteria).presence ===
Presence.Present
"
@update:model-value="
switchCriteria(
criteria,
acmgRatingStore.acmgRating.getCriteriaCNVState(criteria).presence
)
"
hide-details="auto"
density="compact"
class="switch"
>
</v-switch>
<div v-if="ACMG_CRITERIA_CNV_DEFS.get(criteria)?.slider">
<v-slider
:v-model="acmgRatingStore.acmgRating.getCriteriaCNVState(criteria).score"
:min="0"
:max="1"
:step="0.1"
thumb-label
thumb-size="10"
class="slider"
/>
</div>
</td>
<td>{{ ACMG_CRITERIA_CNV_DEFS.get(criteria)?.description ?? '' }}</td>
<td>{{ ACMG_CRITERIA_CNV_DEFS.get(criteria)?.defaultScore }}</td>
<td>{{ ACMG_CRITERIA_CNV_DEFS.get(criteria)?.maxScore }}</td>
</tr>
</tbody>
</v-table>
</v-col>
</v-row>
</div>
<div v-else>
<div class="d-flex align-center justify-center" style="min-height: 300px">
<h3>Loading ACMG information</h3>
<v-progress-circular indeterminate></v-progress-circular>
</div>
</div>
</template>

<style scoped>
.switch {
margin-left: 10px;
padding: 0px;
}
.slider {
margin-top: 10px;
}
</style>
2 changes: 1 addition & 1 deletion frontend/src/components/SvDetails/SvGenes.vue
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ const onRowClicked = (event: Event, { item }: { item: GeneInfo }): void => {

<template>
<div>
<div>
<div style="max-width: 1300px">
<v-data-table
v-model:items-per-page="itemsPerPage"
:headers="headers"
Expand Down
Loading

0 comments on commit 81696da

Please sign in to comment.