4 SGN::Controller::AJAX::Stock - a REST controller class to provide the
5 backend for objects linked with stocks
9 Add new stock properties, stock dbxrefs and so on.
13 Lukas Mueller <lam87@cornell.edu>
14 Naama Menda <nm249@cornell.edu>
19 package SGN
::Controller
::AJAX
::Stock
;
23 use List
::MoreUtils qw
/any /;
25 use CXGN
::Phenome
::Schema
;
26 use CXGN
::Phenome
::Allele
;
27 use CXGN
::Page
::FormattingHelpers qw
/ columnar_table_html info_table_html /;
28 use Scalar
::Util
qw(looks_like_number);
30 BEGIN { extends
'Catalyst::Controller::REST' }
33 default => 'application/json',
35 map => { 'application/json' => 'JSON', 'text/html' => 'JSON' },
42 L<Catalyst::Action::REST> action.
44 Stores a new stockprop in the database
48 sub stockprop
: Local
: ActionClass
('REST') { }
51 my ( $self, $c ) = @_;
53 my $schema = $c->dbic_schema('Bio::Chado::Schema', 'sgn_chado');
54 if ( any
{ $_ eq 'curator' || $_ eq 'submitter' || $_ eq 'sequencer' } $c->user->roles() ) {
57 my $stock_id = $c->req->param('stock_id');
58 my $propvalue = $c->req->param('propvalue');
59 my $type_id = $c->req->param('type_id');
60 my ($existing_prop) = $schema->resultset("Stock::Stockprop")->search( {
61 stock_id
=> $stock_id,
63 value
=> $propvalue, } );
64 if ($existing_prop) { $response = { error
=> 'type_id/propvalue '.$type_id." ".$propvalue." already associated" } ;
67 my $prop_rs = $schema->resultset("Stock::Stockprop")->search( {
68 stock_id
=> $stock_id,
69 type_id
=> $type_id, } );
70 my $rank = $prop_rs ?
$prop_rs->get_column('rank')->max : -1 ;
74 $schema->resultset("Stock::Stockprop")->find_or_create( {
75 stock_id
=> $stock_id,
79 $response = { message
=> "stock_id $stock_id and type_id $type_id associated with value $propvalue", }
81 $response = { error
=> "Failed: $_" }
84 } else { $c->stash->{rest
} = { error
=> 'user does not have a curator/sequencer/submitter account' };
91 my $schema = $c->dbic_schema('Bio::Chado::Schema', 'sgn_chado');
92 my $stock_id = $c->req->param('stock_id');
93 my $stock = $c->stash->{stock
};
95 my $prop_rs = $stock->stockprops(
96 { type_id
=> $type_id, } );
97 # print the prop name and value#
98 $c->stash->{rest
} = ['sucess'];
101 sub associate_locus
:Path
('/ajax/stock/associate_locus') :ActionClass
('REST') {}
103 sub associate_locus_POST
:Args
(0) {
105 $c->stash->{rest
} = { error
=> "Nothing here, it's a POST.." } ;
108 sub associate_locus_GET
:Args
(0) {
109 my ( $self, $c ) = @_;
110 my $stock_id = $c->req->param('object_id');
111 ##my $allele_id = $c->req->param('allele_id');
112 #Phytoene synthase 1 (psy1) Allele: 1
113 #phytoene synthase 1 (psy1)
114 my $locus_input = $c->req->param('loci');
116 my ($locus_data, $allele_symbol) = split (/ Allele: / ,$locus_input);
117 my $is_default = $allele_symbol ?
'f' : 't' ;
118 $locus_data =~ m/(.*)\s\((.*)\)/ ;
120 my $locus_symbol = $2;
122 my ($allele) = $c->dbic_schema('CXGN::Phenome::Schema')
125 locus_symbol
=> $locus_symbol,
127 ->search_related('alleles' , {
128 allele_symbol
=> $allele_symbol,
129 is_default
=> $is_default} );
131 $c->stash->{rest
} = { error
=> "no allele found for locus '$locus_data' (allele: '$allele_symbol')" };
134 my $stock = $c->dbic_schema('Bio::Chado::Schema' , 'sgn_chado')
135 ->resultset("Stock::Stock")->find({stock_id
=> $stock_id } ) ;
136 my $allele_id = $allele->allele_id;
138 $c->stash->{rest
} = { error
=> 'Must be logged in for associating loci! ' };
141 if ( any
{ $_ eq 'curator' || $_ eq 'submitter' || $_ eq 'sequencer' } $c->user->roles() ) {
142 # if this fails, it will throw an acception and will (probably
143 # rightly) be counted as a server error
144 if ($stock && $allele_id) {
146 $stock->create_stockprops(
147 { 'sgn allele_id' => $allele->allele_id },
148 { cv_name
=> 'local', allow_duplicate_values
=> 1, autocreate
=> 1 },
150 $c->stash->{rest
} = ['success'];
151 # need to update the loci div!!
154 $c->stash->{rest
} = { error
=> "Failed: $_" };
158 $c->stash->{rest
} = { error
=> 'need both valid stock_id and allele_id for adding the stockprop! ' };
161 $c->stash->{rest
} = { error
=> 'No privileges for adding new loci. You must have an sgn submitter account. Please contact sgn-feedback@solgenomics.net for upgrading your user account. ' };
165 sub display_alleles
: Chained
('/stock/get_stock') :PathPart
('alleles') : ActionClass
('REST') { }
167 sub display_alleles_GET
{
170 my $stock = $c->stash->{stock
};
171 my $allele_ids = $c->stash->{stockprops
}->{'sgn allele_id'};
172 my $dbh = $c->dbc->dbh;
175 foreach my $allele_id (@
$allele_ids) {
176 my $allele = CXGN
::Phenome
::Allele
->new($dbh, $allele_id);
177 my $phenotype = $allele->get_allele_phenotype();
178 my $allele_link = qq|<a href
="/phenome/allele.pl?allele_id=$allele_id">$phenotype </a
>|;
179 my $locus_id = $allele->get_locus_id;
180 my $locus_name = $allele->get_locus_name;
181 my $locus_link = qq|<a href
="/phenome/locus_display.pl?locus_id=$locus_id">$locus_name </a
>|;
186 $allele->get_allele_name,
191 $hashref->{html
} = @allele_data ?
193 headings
=> [ "Locus name", "Allele symbol", "Phenotype" ],
194 data
=> \
@allele_data,
196 $c->stash->{rest
} = $hashref;
202 sub display_ontologies
: Chained
('/stock/get_stock') :PathPart
('ontologies') : ActionClass
('REST') { }
204 sub display_ontologies_GET
{
206 my $schema = $c->dbic_schema("Bio::Chado::Schema", 'sgn_chado');
207 my $stock = $c->stash->{stock
};
208 my $sp_cvterms = $c->stash->{stock_cvterms
}->{SP
};
209 my $po_cvterms = $c->stash->{stock_cvterms
}->{PO
} ;
210 # should GO be here too?
211 my $go_cvterms = $c->stash->{stock_cvterms
}->{GO
};
213 push @stock_cvterms, @
$sp_cvterms if $sp_cvterms;
214 push @stock_cvterms, @
$po_cvterms if $po_cvterms;
215 ################################
216 ###the following code should be re-formatted in JSON object,
217 #and the html generated in the javascript code
218 ### making this more reusable !
219 ###############################
221 # need to check if the user is logged in, and has editing privileges
224 if ( $c->user->check_roles('curator') || $c->user->check_roles('submitter') || $c->user->check_roles('sequencer') ) { $privileged = 1; }
226 # the ontology term is a stock_cvterm
227 # the evidence details are in stock_cvtermprop (relationship, evidence_code,
228 # evidence_description, evidence_with, reference, obsolete
229 # and the metadata for sp_person_id, create_date, etc.)
231 #keys= cvterms, values= hash of arrays
232 #(keys= ontology details, values= list of evidences)
234 #some cvterms to be used for the evidence codes
235 my $cvterm_rs = $schema->resultset("Cv::Cvterm");
236 my ($rel_cvterm) = $cvterm_rs->search( { name
=> 'relationship'} );
237 my ($evidence_cvterm) = $cvterm_rs->search( { name
=> 'evidence_code' } );
238 # go over the lists of Bio::Chado::Schema::Cv::Cvterm objects
239 # and build the annotation details
240 foreach (@stock_cvterms) {
241 my $cv_name = $_->cvterm->cv->name;
242 my $cvterm_id = $_->cvterm->cvterm_id;
243 my $cvterm_name = $_->cvterm->name;
244 my $db_name = $_->cvterm->dbxref->db->name;
245 my $accession = $_->cvterm->dbxref->accession;
246 my $db_accession = $accession;
247 $db_accession = $cvterm_id if $db_name eq 'SP';
248 my $url = $_->cvterm->dbxref->db->urlprefix . $_->cvterm->dbxref->db->url;
250 qq |<a href
="/chado/cvterm.pl?cvterm_id=$cvterm_id" target
="blank">$cvterm_name</a
>|;
251 # the stock_cvtermprop objects have all the evidence and metadata for the annotation
252 my $props = $_->stock_cvtermprops;
253 my ($relationship_id) = $props->search( { type_id
=>$rel_cvterm->cvterm_id} )->single ?
$props->search( { type_id
=>$rel_cvterm->cvterm_id} )->single->value : undef; # should be 1 relationship per annotation
254 my ($evidence_code_id) = $props->search( { type_id
=> $evidence_cvterm->cvterm_id })->single ?
$props->search( { type_id
=> $evidence_cvterm->cvterm_id })->single->value : undef;
255 # should be 1 evidence_code
257 my $evidence_desc_name;
258 my $rel_name = $relationship_id ?
$cvterm_rs->find({ cvterm_id
=>$relationship_id})->name : undef;
259 my $ev_name = $evidence_code_id ?
$cvterm_rs->find({ cvterm_id
=>$evidence_code_id})->name : undef;
260 #if the cvterm has an obsolete property (must have a true value
261 # since annotations can be obsolete and un-obsolete, it is possible
262 # to have an obsolete property with value = 0, meaning the annotation
264 # build the unobsolete link
265 my ($obsolete_prop) = $props->search(
268 'type.name' => 'obsolete',
270 { join => 'type' } , );
271 if ($obsolete_prop) {
272 my $unobsolete = $privileged ?
qq| <a href
="/ajax/stock/unobsolete_annotation(| . $obsolete_prop->stock_cvtermprop_id . qq|)">[unobsolete
]</a
> | : undef;
273 ### NEED TO MAKE AN AJAX REQUEST #############
274 # onclick: $obsolete_prop->update( {value => '0' } );
276 # generate the list of obsolete annotations
279 . $cvterm_link . " ("
283 my $ontology_details = $rel_name
284 . qq| $cvterm_link ($db_name:<a href
="$url$db_accession" target
="blank"> $accession</a>)<br />|;
285 # build the obsolete link if the user has editing privileges
286 my $obsolete_link = $privileged ?
qq| <a href
="/ajax/stock/obsolete_annotation/| .$_->stock_cvterm_id . qq|/">[delete]</a
> | : undef ;
287 my ($ev_with) = $props->search( {'type.name' => 'evidence_with'} , { join => 'type' } )->single;
288 my $ev_with_dbxref = $ev_with ?
$schema->resultset("General::Dbxref")->find( { dbxref_id
=> $ev_with->value } ) : undef;
289 my $ev_with_url = $ev_with_dbxref ?
$ev_with_dbxref->urlprefix . $ev_with_dbxref->url . $ev_with_dbxref->accession : undef;
290 my $ev_with_acc = $ev_with_dbxref ?
$ev_with_dbxref->accession : undef ;
291 # the reference is a stock_cvterm.pub_id
292 my ($reference) = $_->pub;
293 my $reference_dbxref = $reference ?
$reference->pub_dbxrefs->first->dbxref : undef;
294 my $reference_url = $reference_dbxref ?
$reference_dbxref->db->urlprefix . $reference_dbxref->db->url . $reference_dbxref->accession : undef;
295 my $reference_acc = $reference_dbxref ?
$reference_dbxref->accession : undef;
296 my $display_ref = $reference_acc =~ /^\d/ ?
1 : 0;
297 # the submitter is a sp_person_id prop
298 my ($submitter) = $props->search( {'type.name' => 'sp_person_id'} , { join => 'type' } );
299 my $sp_person_id = $submitter ?
$submitter->value : undef;
300 my $person= CXGN
::People
::Person
->new($c->dbc->dbh, $sp_person_id);
301 my $submitter_info = qq| <a href
="solpeople/personal_info.pl?sp_person_id=$sp_person_id">| . $person->get_first_name . " " . $person->get_last_name . "</a>" ;
302 my ($date) = $props->search( {'type.name' => 'create_date'} , { join => 'type' } )->first || undef ; # $props->search( {'type.name' => 'modified_date'} , { join => 'type' } ) ;
303 my $evidence_date = $date ?
substr $date->value , 0, 10 : undef;
305 # add an empty row if there is more than 1 evidence code
307 $ev_string .= "<hr />" if $ont_hash{$cv_name}{$ontology_details};
308 no warnings
'uninitialized';
309 $ev_string .= $ev_name . "<br />";
310 $ev_string .= $evidence_desc_name . "<br />" if $evidence_desc_name;
311 $ev_string .= "<a href=\"$ev_with_url\">$ev_with_acc</a><br />" if $ev_with_acc;
312 $ev_string .="<a href=\"$reference_url\">$reference_acc</a><br />" if $display_ref;
313 $ev_string .= "$submitter_info $evidence_date $obsolete_link";
314 $ont_hash{$cv_name}{$ontology_details} .= $ev_string;
317 my $ontology_evidence;
319 #now we should have an %ont_hash with all the details we need for printing ...
320 #hash keys are the cv names ..
321 for my $cv_name ( sort keys %ont_hash ) {
323 #and for each ontology annotation create an array ref of evidences
324 for my $ont_detail ( sort keys %{ $ont_hash{$cv_name} } ) {
326 [ $ont_detail, $ont_hash{$cv_name}{$ont_detail} ];
328 my $ev = join "\n", map {
329 qq|<div
class="term">$_->[0]</div
>\n|
330 .qq|<div
class="evidence">$_->[1]</div
>\n|;
332 $ontology_evidence .= info_table_html
(
335 __tableattrs
=> 'width="100%"',
338 #display ontology annotation form
339 if ( @obs_annot && $privileged ) {
340 ##NEED TO RE-WRITE print _obsoleted $ontology_evidence .= print_obsoleted(@obs_annot);
342 $hashref->{html
} = $ontology_evidence;
343 $c->stash->{rest
} = $hashref;
347 sub associate_ontology
:Path
('/ajax/stock/associate_ontology') :ActionClass
('REST') {}
349 sub associate_ontology_POST
:Args
(0) {
351 $c->stash->{rest
} = { error
=> "Nothing here, it's a POST.." } ;
355 sub associate_ontology_GET
:Args
(0) {
356 my ( $self, $c ) = @_;
358 my $params = map { $_ => $c->req->param($_) } qw
/
359 object_id ontology_input relationship evidence_code evidence_description
360 evidence_with reference
363 my $stock_id = $c->req->param('object_id');
364 my $ontology_input = $c->req->param('term_name');
365 my $relationship = $c->req->param('relationship'); # a cvterm_id
366 my $evidence_code = $c->req->param('evidence_code'); # a cvterm_id
367 my $evidence_description = $c->req->param('evidence_description'); # a cvterm_id
368 my $evidence_with = $c->req->param('evidence_with'); # a dbxref_id (type='evidence_with' value = 'dbxref_id'
369 my $logged_user = $c->user;
370 my $logged_person_id = $logged_user->get_object->get_sp_person_id if $logged_user;
372 my $reference = $c->req->param('reference'); # a pub_id
374 my $schema = $c->dbic_schema('Bio::Chado::Schema', 'sgn_chado');
375 my $cvterm_rs = $schema->resultset('Cv::Cvterm');
376 my ($pub) = $reference ?
$reference :
377 $schema->resultset('Pub::Pub')->search( { title
=> 'curator' } )->first; # a pub for 'cuurator' should already be in the sgn database. can add here $curator_cvterm->create_with ... and then create the curator pub with type_id of $curator_cvterm
379 #solanaceae_phenotype--SP:000001--fruit size
380 my ($cv_name, $db_accession, $cvterm_name) = split /--/ , $ontology_input;
381 my ($db_name, $accession) = split ':' , $db_accession;
383 my ($cvterm) = $schema
384 ->resultset('General::Db')
385 ->search({ 'me.name' => $db_name, } )->search_related('dbxrefs' , { accession
=> $accession } )
386 ->search_related('cvterm')->first; # should be only 1 cvterm per dbxref
388 $c->stash->{rest
} = { error
=> "no ontology term found for term $db_name : $accession" };
391 my ($stock) = $c->stash->{stock
} || $schema->resultset("Stock::Stock")->find( { stock_id
=> $stock_id } );
393 my $cvterm_id = $cvterm->cvterm_id;
395 $c->stash->{rest
} = { error
=> 'Must be logged in for associating ontology terms! ' };
398 if ( any
{ $_ eq 'curator' || $_ eq 'submitter' || $_ eq 'sequencer' } $c->user->roles() ) {
399 # if this fails, it will throw an acception and will (probably
400 # rightly) be counted as a server error
401 #########################################################
402 if ($stock && $cvterm_id) {
404 #check if the stock_cvterm exists
405 my $s_cvterm_rs = $stock->search_related(
406 'stock_cvterms', { cvterm_id
=> $cvterm_id, pub_id
=> $pub->pub_id } );
407 # if it exists , we need to increment the rank
409 if ($s_cvterm_rs->first) {
410 $rank = $s_cvterm_rs->get_column('rank')->max + 1;
411 # now check if the evidence codes already exists
412 my ($rel_prop, $ev_prop, $desc_prop, $with_prop);
413 my $eprops = $s_cvterm_rs->search_related('stock_cvtermprops');
414 $rel_prop = $eprops->search( {
415 type_id
=> $cvterm_rs->search( { name
=> 'relationship'})->single->cvterm_id,
416 value
=> $relationship })->first;
418 $ev_prop = $eprops->search( {
419 type_id
=> $cvterm_rs->search( { name
=> 'evidence_code'})->single->cvterm_id,
420 value
=> $evidence_code })->first;
422 $desc_prop = $eprops->search( {
423 type_id
=> $cvterm_rs->search( { name
=> 'evidence description'})->single->cvterm_id,
424 value
=> $evidence_description })->first if $evidence_description;
426 $with_prop = $eprops->search( {
427 type_id
=> $cvterm_rs->search( { name
=> 'evidence_with'})->single->cvterm_id,
428 value
=> $evidence_with })->first if $evidence_with;
430 # return error if annotation + evidence exist
431 if ($rel_prop && $ev_prop) {
432 $c->stash->{rest
} = { error
=> "Annotation exists with these evidence codes! " };
436 # now store a new stock_cvterm
437 my $s_cvterm = $stock->create_related('stock_cvterms', {
438 cvterm_id
=> $cvterm_id,
439 pub_id
=> $pub->pub_id,
442 $s_cvterm->create_stock_cvtermprops(
443 { 'relationship' => $relationship } , { db_name
=> 'OBO_REL', cv_name
=>'relationship' } ) if looks_like_number
($relationship);
444 $s_cvterm->create_stock_cvtermprops(
445 { 'evidence_code' => $evidence_code } , { db_name
=> 'ECO', cv_name
=>'evidence_code' } ) if looks_like_number
($evidence_code);
446 $s_cvterm->create_stock_cvtermprops(
447 { 'evidence_description' => $evidence_description } , { cv_name
=>'null', autocreate
=> 1 } ) if looks_like_number
($evidence_description);
448 $s_cvterm->create_stock_cvtermprops(
449 { 'evidence_with' => $evidence_with } , { cv_name
=>'local' , autocreate
=>1} ) if looks_like_number
($evidence_with);
450 # store the person loading the annotation
451 $s_cvterm->create_stock_cvtermprops(
452 { 'sp_person_id' => $logged_person_id } , { cv_name
=>'local' , autocreate
=>1} );
455 $s_cvterm->create_stock_cvtermprops(
456 { 'create_date' => \
$val } , { cv_name
=>'local' , autocreate
=>1, allow_duplicate_values
=> 1} );
458 $c->stash->{rest
} = ['success'];
461 $c->stash->{rest
} = { error
=> "Failed: $_" };
462 # send an email to sgn bugs
463 $c->stash->{email
} = {
464 to
=> 'sgn-bugs@sgn.cornell.edu',
465 from
=> 'sgn-bugs@sgn.cornell.edu',
466 subject
=> 'Associate ontology failed! Stock_id = $stock_id',
469 $c->forward( $c->view('Email') );
472 # if you reached here this means associate_ontology worked. Now send an email to sgn-db-curation
473 $c->stash->{email
} = {
474 to
=> 'sgn-db-curation@sgn.cornell.edu',
475 from
=> 'sgn-bugs@sgn.cornell.edu',
476 subject
=> 'New ontology term loaded. Stock $stock_id',
477 body
=> "User " . $logged_user->get_object->get_first_name . " " . $logged_user->get_object->get_last_name . "has stored a new ontology term for stock $stock_id http://solgenomics.net/stock/$stock_id/view",
479 $c->forward( $c->view('Email') );
482 $c->stash->{rest
} = { error
=> 'need both valid stock_id and cvterm_id for adding an ontology term to this stock! ' };
485 $c->stash->{rest
} = { error
=> 'No privileges for adding new ontology terms. You must have an sgn submitter account. Please contact sgn-feedback@solgenomics.net for upgrading your user account. ' };
489 sub references
: Chained
('/stock/get_stock') :PathPart
('references') : ActionClass
('REST') { }
492 sub references_GET
:Args
(0) {
494 my $stock = $c->stash->{stock
};
495 # get a list of references
496 my $q = "SELECT dbxref.dbxref_id, pub.pub_id, accession,title
497 FROM public.stock_pub
498 JOIN public.pub USING (pub_id)
499 JOIN public.pub_dbxref USING (pub_id)
500 JOIN public.dbxref USING (dbxref_id)
502 my $sth = $c->dbc->dbh->prepare($q);
503 $sth->execute($stock->get_stock_id);
504 my $response_hash={};
505 while (my ($dbxref_id, $pub_id, $accession, $title) = $sth->fetchrow_array) {
506 $response_hash->{$pub_id} = $accession . ": " . $title;
508 $c->stash->{rest
} = $response_hash;
513 sub obsolete_annotation
:Path
('ajax/stock/obsolete_annotation') : ActionClass
('REST') { }
516 sub obsolete_annotation_POST
:Args
(1) {
517 my ($self, $c, $stock_cvtermprop_id) = @_;
518 my $stock = $c->stash->{stock
};
519 my ($prop) = $stock->stock_cvterms->find_related->('stock_cvtermprops', { stock_cvtermprop_id
=> $stock_cvtermprop_id } );
522 if ($prop && $c->user ) {
523 $prop->update( { value
=> '1' } ) ;
524 $c->stash->{rest
} = ['success'];
526 } else { $response->{error
} = 'stock_cvtermprop $stock_cvtermprop_id does not exists! ';
528 $c->stash->{rest
} = $response;
531 sub unobsolete_annotation
:Path
('ajax/stock/unobsolete_annotation') : ActionClass
('REST') { }
533 sub unobsolete_annotation_POST
:Args
(1) {
534 my ($self, $c, $stock_cvtermprop_id) = @_;
535 my $stock = $c->stash->{stock
};
536 my ($prop) = $stock->stock_cvterms->find_related->('stock_cvtermprops', { stock_cvtermprop_id
=> $stock_cvtermprop_id } );
540 $prop->update( { value
=> '0' } ) ;
541 $c->stash->{rest
} = ['success'];
543 } else { $response->{error
} = 'stock_cvtermprop $stock_cvtermprop_id does not exists! '; }
544 $c->stash->{rest
} = $response;
549 Public Path: /ajax/stock/trait_autocomplete
551 Autocomplete a trait name. Takes a single GET param,
552 C<term>, responds with a JSON array of completions for that term.
553 Finds only traits that exist in nd_experiment_phenotype
557 sub trait_autocomplete
: Local
: ActionClass
('REST') { }
559 sub trait_autocomplete_GET
:Args
(0) {
560 my ( $self, $c ) = @_;
562 my $term = $c->req->param('term');
563 # trim and regularize whitespace
564 $term =~ s/(^\s+|\s+)$//g;
567 my $q = "select distinct cvterm.name from stock join nd_experiment_stock using (stock_id) join nd_experiment_phenotype using (nd_experiment_id) join phenotype using (phenotype_id) join cvterm on cvterm_id = phenotype.observable_id WHERE cvterm.name ilike ?";
568 my $sth = $c->dbc->dbh->prepare($q);
569 $sth->execute( '%'.$term.'%');
570 while (my ($term_name) = $sth->fetchrow_array ) {
571 push @response_list, $term_name;
573 $c->{stash
}->{rest
} = \
@response_list;