Skip to content

Commit

Permalink
schema support for regulatory fusions
Browse files Browse the repository at this point in the history
  • Loading branch information
acoffman committed Jan 3, 2025
1 parent 0a66cd3 commit 9afcd43
Show file tree
Hide file tree
Showing 16 changed files with 216 additions and 39 deletions.
21 changes: 17 additions & 4 deletions server/app/graphql/mutations/create_fusion_feature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,22 @@ def ready?(organization_id: nil, five_prime_gene:, three_prime_gene:,**kwargs)

#check that partner status matches gene_id presence
[five_prime_gene, three_prime_gene].each do |gene_input|
if gene_input.gene_id.present? && gene_input.partner_status != 'known'
raise GraphQL::ExecutionError, "Partner status needs to be 'known' if a gene_id is set"
if gene_input.gene_id.present? && (gene_input.partner_status != 'known' || gene_input.partner_status != 'regulatory')
raise GraphQL::ExecutionError, "Partner status needs to be 'known' or 'regulatory' if a gene_id is set"
end
if gene_input.gene_id.blank? && gene_input.partner_status == 'known'
raise GraphQL::ExecutionError, "Partner status can't be 'known' if a gene_id is not set"
if gene_input.gene_id.blank? && (gene_input.partner_status == 'known' && gene_input.partner_status != 'regulatory')
raise GraphQL::ExecutionError, "Partner status can't be 'known' or 'regulatory' if a gene_id is not set"
end
end

#check that maximuim one gene has regulatory_fusion_type set

Check failure on line 47 in server/app/graphql/mutations/create_fusion_feature.rb

View workflow job for this annotation

GitHub Actions / Check for spelling errors

maximuim ==> maximum
if five_prime_gene.partner_status == 'regulatory' && three_prime_gene.partner_status == 'regulatory'
raise GraphQL::ExecutionError, "Only one Fusion partner can be marked 'regulatory'"
end
if five_prime_gene.regulatory_fusion_type.present? && three_prime_gene.regulatory_fusion_type.present?
raise GraphQL::ExecutionError, "Only one Fusion partner can have a regulatory fusion type set."
end

return true
end

Expand All @@ -54,12 +62,16 @@ def authorized?(organization_id: nil, **kwargs)

def resolve(five_prime_gene:, three_prime_gene:, organization_id: nil)

#only one can be set
regulatory_fusion_type = five_prime_partner_status.regulatory_fusion_type || three_prime_partner_status.regulatory_fusion_type

existing_feature_instance = Features::Fusion
.find_by(
five_prime_gene_id: five_prime_gene.gene_id,
three_prime_gene_id: three_prime_gene.gene_id,
five_prime_partner_status: five_prime_gene.partner_status,
three_prime_partner_status: three_prime_gene.partner_status,
regulatory_fusion_type: regulatory_fusion_type
)

if existing_feature_instance.present?
Expand All @@ -74,6 +86,7 @@ def resolve(five_prime_gene:, three_prime_gene:, organization_id: nil)
three_prime_gene_id: three_prime_gene.gene_id,
five_prime_partner_status: five_prime_gene.partner_status,
three_prime_partner_status: three_prime_gene.partner_status,
regulatory_fusion_type: regulatory_fusion_type,
originating_user: context[:current_user],
organization_id: organization_id,
)
Expand Down
2 changes: 2 additions & 0 deletions server/app/graphql/types/entities/fusion_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ class FusionType < Types::Entities::FeatureType
field :five_prime_partner_status, Types::Fusion::FusionPartnerStatus, null: false
field :three_prime_partner_status, Types::Fusion::FusionPartnerStatus, null: false

field :regulatory_fusion_type, Types::Fusion::RegulatoryFusionTypeType, null: true

def five_prime_gene
Loaders::AssociationLoader.for(Features::Fusion, :five_prime_gene).load(object)
end
Expand Down
2 changes: 2 additions & 0 deletions server/app/graphql/types/fusion/fusion_partner_input_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@ class FusionPartnerInputType < Types::BaseInputObject
description: 'The status of the fusion partner'
argument :gene_id, Int, required: false,
description: 'The CIViC gene ID of the partner, if known'
argument :regulatory_fusion_type, Types::Fusion::RegulatoryFusionTypeType, required: false,
description: "If the fusion partner status is set to regulatory, what type of regulatory fusion is it?"
end
end
1 change: 1 addition & 0 deletions server/app/graphql/types/fusion/fusion_partner_status.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ class FusionPartnerStatus < Types::BaseEnum
value 'KNOWN', value: 'known'
value 'UNKNOWN', value: 'unknown'
value 'MULTIPLE', value: 'multiple'
value 'REGULATORY', value: 'regulatory'
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Types::Fusion
class RegulatoryFusionTypeType < Types::BaseEnum
Constants::REGULATORY_FUSION_ENUM_TYPES.each do |(name, _)|
value name.upcase, value: name
end
end
end
10 changes: 8 additions & 2 deletions server/app/models/actions/create_fusion_feature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ module Actions
class CreateFusionFeature
include Actions::Transactional

attr_reader :feature, :originating_user, :organization_id, :create_variant, :five_prime_partner_status, :three_prime_partner_status
attr_reader :feature, :originating_user, :organization_id, :create_variant, :five_prime_partner_status, :three_prime_partner_status, :regulatory_fusion_type

def initialize(originating_user:, five_prime_gene_id:, three_prime_gene_id:, five_prime_partner_status:, three_prime_partner_status:, organization_id: nil, create_variant: true)
def initialize(originating_user:, five_prime_gene_id:, three_prime_gene_id:, five_prime_partner_status:, three_prime_partner_status:, regulatory_fusion_type:, organization_id: nil, create_variant: true)
feature_name = "#{construct_fusion_partner_name(five_prime_gene_id, five_prime_partner_status)}::#{construct_fusion_partner_name(three_prime_gene_id, three_prime_partner_status)}"
@feature = Feature.new(
name: feature_name,
Expand All @@ -14,10 +14,12 @@ def initialize(originating_user:, five_prime_gene_id:, three_prime_gene_id:, fiv
three_prime_gene_id: three_prime_gene_id,
five_prime_partner_status: five_prime_partner_status,
three_prime_partner_status: three_prime_partner_status,
regulatory_fusion_type: regulatory_fusion_type,
feature: feature,
)
@five_prime_partner_status = five_prime_partner_status
@three_prime_partner_status = three_prime_partner_status
@regulatory_fusion_type = regulatory_fusion_type
@originating_user = originating_user
@organization_id = organization_id
@create_variant = create_variant
Expand All @@ -26,6 +28,10 @@ def initialize(originating_user:, five_prime_gene_id:, three_prime_gene_id:, fiv
def construct_fusion_partner_name(gene_id, partner_status)
if partner_status == 'known'
Features::Gene.find(gene_id).name
elseif partner_status == 'regulatory'
gene_name = Features::Gene.find(gene_id).name
rft = Features::Fusion.format_regulatory_fusion_type(regulatory_fusion_type)
"#{rft}@#{gene_name}"
elsif partner_status == 'unknown'
'?'
elsif partner_status == 'multiple'
Expand Down
6 changes: 4 additions & 2 deletions server/app/models/activities/create_fusion_feature.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
module Activities
class CreateFusionFeature < Base
attr_reader :feature, :five_prime_gene_id, :three_prime_gene_id, :five_prime_partner_status, :three_prime_partner_status, :create_variant
attr_reader :feature, :five_prime_gene_id, :three_prime_gene_id, :five_prime_partner_status, :three_prime_partner_status, :create_variant, :regulatory_fusion_type

def initialize(originating_user:, organization_id:, five_prime_gene_id:, three_prime_gene_id:, five_prime_partner_status:, three_prime_partner_status:, create_variant: true)
def initialize(originating_user:, organization_id:, five_prime_gene_id:, three_prime_gene_id:, five_prime_partner_status:, three_prime_partner_status:, regulatory_fusion_type:, create_variant: true)
super(organization_id: organization_id, user: originating_user)
@five_prime_gene_id = five_prime_gene_id
@three_prime_gene_id = three_prime_gene_id
@five_prime_partner_status = five_prime_partner_status
@three_prime_partner_status = three_prime_partner_status
@regulatory_fusion_type = regulatory_fusion_type
@create_variant = create_variant
end

Expand All @@ -25,6 +26,7 @@ def call_actions
three_prime_gene_id: three_prime_gene_id,
five_prime_partner_status: five_prime_partner_status,
three_prime_partner_status: three_prime_partner_status,
regulatory_fusion_type: regulatory_fusion_type,
originating_user: user,
organization_id: organization&.id,
create_variant: create_variant
Expand Down
34 changes: 34 additions & 0 deletions server/app/models/constants.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,38 @@ module Constants
ENSEMBL_TRANSCRIPT_ID_FORMAT = /\AENST\d{11}\.\d{1,2}\z/

REPRESENTATIVE_FUSION_VARIANT_NAME = 'Fusion'

# INSDC regulatory class vocabulary as required here: https://fusions.cancervariants.org/en/latest/nomenclature.html#regulatory-nomenclature
REGULATORY_FUSION_TYPES = [
["attenuator", "SO:0000140"],
["CAAT_signal", "SO:0000172"],
["DNase_I_hypersensitive_site", "SO:0000685"],
["enhancer", "SO:0000165"],
["enhancer_blocking_element", nil],
["GC_signal", "SO:0000173"],
["imprinting_control_region", nil],
["insulator", "SO:0000627"],
["locus_control_region", "SO:0000037"],
["matrix_attachment_region", "SO:0000036"],
["minus_35_signal", "SO:0000176"],
["minus_10_signal", "SO:0000175"],
["polyA_signal_sequence", "SO:0000551"],
["promoter", "SO:0000167"],
["recoding_stimulatory_region", "SO:1001268"],
["recombination_enhancer", "SO:0002059"],
["replication_regulatory_region", "SO:0001682"],
["response_element", nil],
["ribosome_binding_site", "SO:0000552"],
["riboswitch", "SO:0000035"],
["silencer", "SO:0000625"],
["TATA_box", "SO:0000174"],
["terminator", "SO:0000141"],
["transcriptional_cis_regulatory_region", "SO:0001055"],
["uORF", "SO:0002027"],
["other", nil]
].map { |(type, soid)| ["reg_#{type}", soid] }

REGULATORY_FUSION_ENUM_TYPES = REGULATORY_FUSION_TYPES.map { |(type, _)| [type, type] }.to_h

FUSION_PARTNER_STATUSES = [ 'known', 'unknown', 'multiple', 'regulatory' ].map { [_1, _1] }.to_h
end
42 changes: 13 additions & 29 deletions server/app/models/features/fusion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,49 +6,33 @@ class Fusion < ActiveRecord::Base
belongs_to :five_prime_gene, class_name: 'Features::Gene', optional: true
belongs_to :three_prime_gene, class_name: 'Features::Gene', optional: true

enum five_prime_partner_status: {
known: 'known',
unknown: 'unknown',
multiple: 'multiple',
}, _prefix: true
enum five_prime_partner_status: Constants::FUSION_PARTNER_STATUSES, _prefix: true
enum three_prime_partner_status: Constants::FUSION_PARTNER_STATUSES, _prefix: true

enum three_prime_partner_status: {
known: 'known',
unknown: 'unknown',
multiple: 'multiple',
}, _prefix: true
enum regulatory_fusion_type: Constants::REGULATORY_FUSION_ENUM_TYPES

has_many :variant_groups
has_many :source_suggestions

#TODO - move to feature?
has_many :comment_mentions, foreign_key: :comment_id, class_name: 'EntityMention'

validate :partner_status_valid_for_gene_ids
validate :at_least_one_gene_id
validates_with FusionFeatureValidator

def partner_status_valid_for_gene_ids
if !self.in_revision_validation_context
[self.five_prime_gene, self.three_prime_gene].zip([self.five_prime_partner_status, self.three_prime_partner_status], [:five_prime_gene, :three_prime_gene]).each do |gene, status, fk|
if gene.nil? && status == 'known'
errors.add(fk, "Partner status cannot be 'known' if the gene isn't set")
elsif !gene.nil? && status != 'known'
errors.add(fk, "Partner status has to be 'known' if gene is set")
end
end
end
def display_name
name
end

def at_least_one_gene_id
if !self.in_revision_validation_context && self.five_prime_gene_id.nil? && self.three_prime_gene_id.nil?
errors.add(:base, "One or both of the genes need to be set")
def self.format_regulatory_fusion_type(rft)
if rft == 'reg_enhancer'
'reg_e'
elsif rft == 'reg_promoter'
'reg_p'
else
rft
end
end

def display_name
name
end

def editable_fields
[
:description,
Expand Down
1 change: 1 addition & 0 deletions server/app/models/variant_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ class VariantType < ActiveRecord::Base

has_and_belongs_to_many :variants
has_and_belongs_to_many :pipeline_types
enum regulatory_fusion_type: Constants::REGULATORY_FUSION_ENUM_TYPES

def url
if self.soid != "N/A"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
class AddRegulatoryFusionTypesEnum < ActiveRecord::Migration[7.1]
def up
create_enum :regulatory_fusion_types, Constants::REGULATORY_FUSION_TYPES.map(&:first)
add_enum_value :fusion_partner_status, "regulatory"

add_column :variant_types, :regulatory_fusion_type, :enum, enum_type: :regulatory_fusion_types, null: true
add_column :fusions, :regulatory_fusion_type, :enum, enum_type: :regulatory_fusion_types, null: true
add_index :variant_types, :regulatory_fusion_type

Constants::REGULATORY_FUSION_TYPES.each do |(type, soid)|
if soid.present?
vt = VariantType.find_by!(soid: soid)
vt.regulatory_fusion_type = type
vt.save!
end
end
end

def down
remove_column :variant_types, :regulatory_fusion_type
remove_column :fusions, :regulatory_fusion_type
execute <<-SQL
DROP TYPE regulatory_fusion_types;
SQL
end
end
23 changes: 21 additions & 2 deletions server/db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.1].define(version: 2024_09_14_154057) do
ActiveRecord::Schema[7.1].define(version: 2025_01_02_170055) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"

# Custom types defined in this database.
# Note that some types may not work with other database engines. Be careful if changing database.
create_enum "exon_coordinate_record_state", ["stub", "exons_provided", "fully_curated"]
create_enum "exon_offset_direction", ["positive", "negative"]
create_enum "fusion_partner_status", ["known", "unknown", "multiple"]
create_enum "fusion_partner_status", ["known", "unknown", "multiple", "regulatory"]
create_enum "regulatory_fusion_partner", ["five_prime", "three_prime"]
create_enum "regulatory_fusion_types", ["reg_attenuator", "reg_CAAT_signal", "reg_DNase_I_hypersensitive_site", "reg_enhancer", "reg_enhancer_blocking_element", "reg_GC_signal", "reg_imprinting_control_region", "reg_insulator", "reg_locus_control_region", "reg_matrix_attachment_region", "reg_minus_35_signal", "reg_minus_10_signal", "reg_polyA_signal_sequence", "reg_promoter", "reg_recoding_stimulatory_region", "reg_recombination_enhancer", "reg_replication_regulatory_region", "reg_response_element", "reg_ribosome_binding_site", "reg_riboswitch", "reg_silencer", "reg_TATA_box", "reg_terminator", "reg_transcriptional_cis_regulatory_region", "reg_uORF", "reg_other"]
create_enum "variant_coordinate_record_state", ["stub", "fully_curated"]

create_table "acmg_codes", id: :serial, force: :cascade do |t|
Expand Down Expand Up @@ -106,6 +108,20 @@
t.index ["user_id"], name: "index_affiliations_on_user_id"
end

create_table "api_keys", force: :cascade do |t|
t.string "bearer_type"
t.bigint "bearer_id"
t.text "token_prefix", null: false
t.text "token_suffix", null: false
t.text "token_digest", null: false
t.boolean "revoked", default: false, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["bearer_type", "bearer_id"], name: "index_api_keys_on_bearer"
t.index ["revoked"], name: "index_api_keys_on_revoked"
t.index ["token_digest"], name: "index_api_keys_on_token_digest", unique: true
end

create_table "assertions", id: :serial, force: :cascade do |t|
t.text "description"
t.datetime "created_at", precision: nil
Expand Down Expand Up @@ -536,6 +552,7 @@
t.enum "three_prime_partner_status", default: "unknown", null: false, enum_type: "fusion_partner_status"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.enum "regulatory_fusion_type", enum_type: "regulatory_fusion_types"
t.index ["five_prime_gene_id"], name: "index_fusions_on_five_prime_gene_id"
t.index ["three_prime_gene_id"], name: "index_fusions_on_three_prime_gene_id"
end
Expand Down Expand Up @@ -963,8 +980,10 @@
t.integer "parent_id"
t.integer "lft"
t.integer "rgt"
t.enum "regulatory_fusion_type", enum_type: "regulatory_fusion_types"
t.index ["display_name"], name: "index_variant_types_on_display_name"
t.index ["name"], name: "index_variant_types_on_name"
t.index ["regulatory_fusion_type"], name: "index_variant_types_on_regulatory_fusion_type"
t.index ["soid"], name: "index_variant_types_on_soid"
end

Expand Down
27 changes: 27 additions & 0 deletions server/test/fixtures/feature_fusions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
braf_vhl_fusion:
five_prime_gene: braf
three_prime_gene: vhl
five_prime_partner_status: known
three_prime_partner_status: known
regulatory_fusion_type: reg_enhancer

braf_fusion:
five_prime_gene: braf
three_prime_gene: null
five_prime_partner_status: known
three_prime_partner_status: unknown
regulatory_fusion_type: null

vhl_fusion:
five_prime_gene: null
three_prime_gene: vhl
five_prime_partner_status: unknown
three_prime_partner_status: known
regulatory_fusion_type: null

regulatory_fusion:
five_prime_gene: braf
three_prime_gene: vhl
five_prime_partner_status: regulatory
three_prime_partner_status: known
regulatory_fusion_type: reg_enhancer
5 changes: 5 additions & 0 deletions server/test/fixtures/features.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ msi:
full_name: Microsatellite Instability
description: The description for the MSI factor
feature_instance: msi (Features::Factor)

braf_fusion:
name: BRAF::?
description: BRAF and unknown partner
feature_instance: braf_fusion (Features::Fusion)
Loading

0 comments on commit 9afcd43

Please sign in to comment.